在普通/同步/单线程控制台应用程序中,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
,因为此时没有实际执行的任务)。
线程亲和性也有其自身的问题。实际上,每个独立的异步操作最终都需要一个单独的线程。再见,可扩展性。。。