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

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

例如:

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] INFO程序块1 – 开始工作

5279 [9] INFO计划块1 – 完成工作

5279 [9] INFO程序块2 – 开始工作

10292 [9] INFO计划块2 – 完成工作

10292 [9] INFO程序块3 – 开始工作

15299 [9] INFO计划块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] INFO程序块1 – 开始工作

265 [10] INFO程序块2 – 开始工作

265 [10] INFO程序块3 – 开始工作

5280 [7] INFO计划(null) – 完成工作

5280 [12] INFO计划(null) – 完成工作

5280 [12] INFO计划(null) – 完成工作

除了向log4net添加新的TaskContext(或类似内容)之外,有没有办法跟踪这种活动?

目标是使用async / await语法糖来实现这一目标 – 要么强制某种线程关联,要么像保持任务键入的并发字典这样可能是可行的选项,但我试图保持尽可能接近代码的同步版本尽可能。 🙂

目前没有关于async逻辑调用上下文的好故事。

CallContext不能用于此。 逻辑CallContext不理解async方法如何早期返回并稍后恢复,因此对于使用简单并行性的代码(如Task.WhenAll ,它不会始终正常工作。

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

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

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

与此同时,请提出微软为此提供一些机制的建议 。

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

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