如何使用async/await方法管理类似NDC的log4net堆栈?(每个任务堆栈?)



在普通/同步/单线程控制台应用程序中,NDC.Push可以很好地管理"当前项"(可能在多个嵌套级别,但本例仅为一个级别)。

例如:

private static ILog s_logger = LogManager.GetLogger("Program");
static void Main(string[] args)
{
    BasicConfigurator.Configure();
    DoSomeWork("chunk 1");
    DoSomeWork("chunk 2");
    DoSomeWork("chunk 3");
}
static void DoSomeWork(string chunkName)
{
    using (NDC.Push(chunkName))
    {
        s_logger.Info("Starting to do work");
        Thread.Sleep(5000);
        s_logger.Info("Finishing work");
    }
}

这将导致预期的日志输出,在"程序"(基本配置程序的默认模式)右侧显示"区块X"NDC条目

232[9]信息程序块1-开始工作

5279[9]信息程序块1-完成工作

5279[9]信息程序块2-开始工作

10292[9]信息程序块2-完成工作

10292[9]信息程序块3-开始工作

15299[9]信息程序块3-完成工作

然而,我不知道如何使用"普通"异步方法来维护它。

例如,尝试这样做:

private static ILog s_logger = LogManager.GetLogger("Program");
static void Main(string[] args)
{
    BasicConfigurator.Configure();
    var task1 = DoSomeWork("chunk 1");
    var task2 = DoSomeWork("chunk 2");
    var task3 = DoSomeWork("chunk 3");
    Task.WaitAll(task1, task2, task3);
}
static async Task DoSomeWork(string chunkName)
{
    using (log4net.LogicalThreadContext.Stacks["NDC"].Push(chunkName))
    //using (log4net.ThreadContext.Stacks["NDC"].Push(chunkName))
    {
        s_logger.Info("Starting to do work");
        await Task.Delay(5000);
        s_logger.Info("Finishing work");
    }
}

将它们全部显示为"开始";通常";,但当任务在另一个线程上完成时,堆栈就会丢失(我希望log4net.LogicalThreadContext是TPL——我想是‘ware’)。

234[10]信息程序块1-开始工作

265[10]信息程序块2-开始工作

265[10]信息程序块3-开始工作

5280[7]信息程序(空)-完成工作

5280[12]信息程序(空)-完成工作

5280[12]信息程序(空)-完成工作

除了在log4net中添加一个新的TaskContext(或类似的)之外,有没有一种方法可以跟踪这种活动?

我们的目标实际上是使用async/await语法糖来实现这一点——要么强制某种线程亲和性,要么做一些事情,比如保持一个由任务键控的并发字典,这些都是可行的选择,但我正在努力尽可能接近代码的同步版本。:)

async逻辑调用上下文目前还没有一个好的例子。

CallContext不能用于此。逻辑CallContext不理解async方法是如何提前返回并稍后恢复的,因此对于使用简单并行性的代码(如Task.WhenAll),它并不总是正确工作

更新: CallContext已在.NET 4.5 RTW中更新,以正确使用async方法。

我查看了log4net;LogicalThreadContext被记录为使用CallContext,但有一个错误使其使用非逻辑上下文(2012年2月2日在其SVN中修复;当前1.2.11版本不包括该修复)。然而,即使它被修复了,它仍然不能与async一起工作(因为逻辑CallContext不与async一起工作)。

当我需要async逻辑调用上下文时,我会创建一个包含上下文数据的类,并将我的所有async方法作为该类的实例成员保持为函数样式。这当然不是一个理想的解决方案,但它是一个有效的肮脏黑客。

同时,请对微软为此提供某种机制的建议投赞成票。

p.S.由Task键控的并发字典不起作用,因为async方法不一定在运行任务(即,在示例代码中,在using语句中,Task.CurrentId将是null,因为此时没有实际执行的任务)。

线程亲和性也有其自身的问题。实际上,每个独立的异步操作最终都需要一个单独的线程。再见,可扩展性。。。

最新更新