为什么ThreadStatic数据在线程之间意外共享?

时间:2011-12-15 23:39:12

标签: c# multithreading c#-4.0 thread-safety threadstatic

我有一个我编写的日志框架,它能够跟踪“日志记录上下文”。它有一个可插拔的策略框架,但我最常使用的是一个ThreadStatic变体,用于跟踪[ThreadStatic]变量中的上下文。我一直在尝试解决多线程工作流中的日志记录上下文问题。目标是让所有共享公共线程的方法和类的所有调用的所有日志条目记录相同的上下文信息。由于每个线程理论上应该获得自己的ThreadStatic变量,因此这个想法似乎很容易。

public class ThreadStaticLoggingContextStrategy: ILoggingContextStrategy
{
    public ThreadStaticLoggingContextStrategy()
    {
        Debug.WriteLine("[INITIALIZE] A new instance of 'ThreadStaticLoggingContextStrategy' has been created.");
    }

    [ThreadStatic] private LoggingContext _context;

    public LoggingContext GetLoggingContext()
    {
        if (_context == null)
            _context = new LoggingContext();

        return _context;
    }
}

实际上,似乎ThreadStatic数据实际上是跨线程共享的。这违背了我对线程的理解。我很难搞清楚问题是什么,直到我投入额外的日志条目,跟踪每个线程清除线程上下文时(所有线程在主循环上运行...在开始时,如果必要的消息是收到,上下文初始化,最后在finally子句中,重置。)以下日志记录是一致的:

  

[2011-12-15 16:27:21,233] [调查]   [TPI.LTI.Eventing.GroupCreatedNotificationHandler:   TPI.LTI.Provisioning.Handlers.GroupCreatedNotificationHandler.WORKDEVELOPMENT.1_Thread:   324]:(ContextId = 184e82dd-152b-4bb5-a2c6-3e05b2365c04;   TRANSACTIONID = 1a11130e-e8dd-4fa1-9107-3b46dcb4ffd6;   HandlerName = GroupCreatedNotificationHandler;   HandlerId = WORKDEVELOPMENT.1)推送工具事件   '0967e031-398f-437d-8949-2a17fe844df0'到   http://tpidev.pearsoncmg.com/tpi/lti/service/event ...

     

[2011-12-15 16:27:21,259] [DEBUG] [TPI.LTI.Facades.LTIFacade:   TPI.LTI.Provisioning.Handlers.GroupCreatedNotificationHandler.WORKDEVELOPMENT.1_Thread:   299]:(ContextId = 184e82dd-152b-4bb5-a2c6-3e05b2365c04;   TRANSACTIONID = 1a11130e-e8dd-4fa1-9107-3b46dcb4ffd6;   HandlerName = GroupCreatedNotificationHandler;   HandlerId = WORKDEVELOPMENT.1)获取工具的LTI工具实例   实例guid 0967e031-398f-437d-8949-2a17fe844df0:

     

[2011-12-15 16:27:21,318] [DEBUG] [TPI.LTI.Facades.LTIFacade:   TPI.LTI.Provisioning.Handlers.GroupCreatedNotificationHandler.WORKDEVELOPMENT.1_Thread:   299]:(ContextId = 184e82dd-152b-4bb5-a2c6-3e05b2365c04;   TRANSACTIONID = 1a11130e-e8dd-4fa1-9107-3b46dcb4ffd6;   HandlerName = GroupCreatedNotificationHandler;   HandlerId = WORKDEVELOPMENT.1)找到工具实例的LTI工具实例   guid 0967e031-398f-437d-8949-2a17fe844df0。

     

[2011-12-15 16:27:21,352] [DEBUG] [TPI.LTI.Facades.TPIFacade:   TPI.LTI.Provisioning.Handlers.GroupCreatedNotificationHandler.WORKDEVELOPMENT.1_Thread:   299]:(ContextId = 184e82dd-152b-4bb5-a2c6-3e05b2365c04;   TRANSACTIONID = 1a11130e-e8dd-4fa1-9107-3b46dcb4ffd6;   HandlerName = GroupCreatedNotificationHandler;   HandlerId = WORKDEVELOPMENT.1)将事件发布到TPI   的 'http://tpidev.pearsoncmg.com/tpi/lti/service/event' ...

     

[2011-12-15 16:27:21,428] [调查]   [TPI.LTI.Eventing.GroupCreatedNotificationHandler:   TPI.LTI.Provisioning.Handlers.GroupCreatedNotificationHandler.WORKDEVELOPMENT.2_Thread:   301]:    [日志]重置日志记录上下文!!

     

[2011-12-15 16:27:21,442] [调查]   [TPI.LTI.Eventing.GroupCreatedNotificationHandler:   TPI.LTI.Provisioning.Handlers.GroupCreatedNotificationHandler.WORKDEVELOPMENT.2_Thread:   299]:队列中没有待处理的消息。   GroupCreatedNotificationHandler.WORKDEVELOPMENT.2处理程序是   等待...

     

[2011-12-15 16:27:22,282] [DEBUG] [TPI.LTI.Facades.TPIFacade:   TPI.LTI.Provisioning.Handlers.GroupCreatedNotificationHandler.WORKDEVELOPMENT.1_Thread:   301]:活动发布到TPI。

     

[2011-12-15 16:27:22,283] [调查]   [TPI.LTI.Eventing.GroupCreatedNotificationHandler:   TPI.LTI.Provisioning.Handlers.GroupCreatedNotificationHandler.WORKDEVELOPMENT.1_Thread:   301]:收到提供者的回复:

您可以看到在这种特殊情况下有两个线程,1_Thread和2_Thread。我用斜体显示了应该包含在1_Thread的每个日志条目开头的上下文数据。我已经在2_Thread中加粗了重置日志记录上下文的点。在那之后,缺少1_Thread的所有上下文信息。到目前为止,在几十个测试中,在另一个上重置日志记录上下文后,所有线程的上下文信息都会丢失。

我误解了ThreadStatic吗?我从2001年开始编写C#代码,之前我从未经历过这种行为。似乎.NET 4中有一个新的ThreadLocal<T>类,但是我不确定它是否只是内部使用了ThreadStatic,因此会出现同样的问题,或者它的功能是否有所不同(希望更可靠)。对这个问题的任何见解都会非常令人满意!谢谢!

2 个答案:

答案 0 :(得分:15)

因为该字段不是静态的。它仅适用于静态字段。

如果这是4.0,可以查看ThreadLocal<T>

答案 1 :(得分:0)

正如@MarcGravell指出你的领域不是静态的,这是你问题的原因。

但是,如果消费者要从GetLoggingContext存储返回值,则可以在线程之间共享此值。我通常尝试使用任何ThreadStatic变量作为类的实现细节,而不是将它们暴露在它之外。