ThreadLocal并等待

时间:2014-10-02 13:32:03

标签: c# multithreading async-await

所以我们有一个日志系统,我们使用Log.Info并写入ILogger

现在我们有多个工作人员在后台运行,我们希望这些工作人员写入他们自己的日志。所以每个工人都捆绑在一起。执行此任务时记录的所有内容都应转发给它自己的记录器。

我们考虑制作一个方法Log.SetLoggerForCurrentThread,用ThreadLocal实现它。执行代码看起来像这样:

public class Worker
{
    ILogger _Logger;

    public void ExecuteTask()
    {
        Log.Info( "This goes to the 'Global' logger" );

        using ( Log.SetLoggerForCurrentThread(_Logger) )
        {
             Log.Info( "This goes to local logger" );
             DoWork();
        }
    }

    private async void DoWork()
    {
        Log.Info( "Starting..." );

        // SomeMethod does some logging, 
        // that also needs to be forwared to the local logger
        var value = await SomeDeepDomainClass.SomeMethod();

        // if we use ThreadLocal, and this thread has been reused, 
        // it could be a completely different logger that is now attached.
        Log.Info( "Ended..." );
    }
}

问题

  • 当我们使用await时,线程可以在理论上处理另一个worker的工作,从而混合本地记录器。
  • 做类似事情的最佳模式是什么?我可以使用哪种存储方式?
  • CultureInfo如何处理此问题?

背景资料

这些Workers中的大多数将在Azure WorkerRole实例中运行,但现在和它们也是从控制台应用程序触发(一次)。

2 个答案:

答案 0 :(得分:6)

您可以使用CallContext跨线程传递(可序列化)数据。请参阅此文章以获取示例:

http://blog.stephencleary.com/2013/04/implicit-async-context-asynclocal.html

有关背景信息,请参阅此文:

http://blogs.msdn.com/b/pfxteam/archive/2012/06/15/executioncontext-vs-synchronizationcontext.aspx

答案 1 :(得分:6)

在我看来,最好的解决方案是将记录器实例作为参数(或成员变量)传递,或者注入它们(例如,使用嵌套的范围)。

但是,如果要以与await兼容的方式隐式存储和传递日志记录实例,则需要使用逻辑调用上下文。我有一个blog post describing this approach,它指出了这种方法的局限性:

  1. 它仅适用于完整的.NET 4.5框架。
  2. 您必须使用“覆盖”语义。这通常意味着只存储不可变数据。
  3. 考虑到这一点,这里有一些代码可以满足您的需求:

    public static class LocalLogger
    {
      private static readonly string name = Guid.NewGuid().ToString("N");
    
      // Static Log methods should read this.
      public static ILogger CurrentLogger
      {
        public get
        {
          var ret = CallContext.LogicalGetData(name) as ILogger;
          return ret == null ? Logger.GlobalLogger : ret;
        }
    
        private set
        {
          CallContext.LogicalSetData(name, value);
        }
      }
    
      // Client code uses this.
      public static IDisposable UseLogger(ILogger logger)
      {
        var oldLogger = CurrentLogger;
        CurrentLogger = logger;
        if (oldLogger == GlobalLogger)
          return NoopDisposable.Instance;
        return new SetWhenDisposed(oldLogger);
      }
    
      private sealed class NoopDisposable : IDisposable
      {
        public void Dispose() { }
        public static readonly Instance = new NoopDisposable();
      }
    
      private sealed class SetWhenDisposed : IDisposable
      {
        private readonly ILogger _oldLogger;
        private bool _disposed;
    
        public SetWhenDisposed(ILogger oldLogger)
        {
          _oldLogger = oldLogger;
        }
    
        public void Dispose()
        {
          if (_disposed)
            return;
          CurrentLogger = _oldLogger;
          _disposed = true;
        }
      }
    }