Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ThreadLocal and await

We have a logging system where we use Log.Info and it writes to an ILogger.

Now we have multiple workers running in the background, and we want these to write to their own logs. So everything is bundled per Worker. Everything that is logged when this task is being executed should be forwarded to its own logger.

We were thinking about making a method Log.SetLoggerForCurrentThread, implementing it with ThreadLocal. The executing code would look something like this:

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..." );
    }
}

Questions

  • When we use await, the thread could in theory process work on another worker, thus mixing up the local loggers.
  • What is the best pattern to do something similar? What storage method can I use?
  • How does CultureInfo handle this?

Background information

Most of these Workers will run within an Azure WorkerRole instance, but now and then they are also triggered (once) from a Console application.

like image 420
Dirk Boer Avatar asked Oct 02 '14 13:10

Dirk Boer


People also ask

Is Asynclocal thread safe?

This type is thread-safe for all members.

What is threadLocal in java?

The ThreadLocal class is used to create thread local variables which can only be read and written by the same thread. For example, if two threads are accessing code having reference to same threadLocal variable then each thread will not see any modification to threadLocal variable done by other thread.

What is threadLocal in C#?

Thread-local storage (TLS) is a computer programming method that uses static or global memory local to a thread. All threads of a process share the virtual address space of the process. The local variables of a function are unique to each thread that runs the function.

What is Asynclocal in C#?

Represents ambient data that is local to a given asynchronous control flow, such as an asynchronous method.


2 Answers

You can use CallContext to pass (serializable) data across threads. See this article for an example:
https://blog.stephencleary.com/2013/04/implicit-async-context-asynclocal.html

For some background information, see this article:
https://devblogs.microsoft.com/pfxteam/executioncontext-vs-synchronizationcontext/

like image 135
user1620220 Avatar answered Sep 18 '22 17:09

user1620220


In my opinion, the best solution is to either pass the logger instances as arguments (or member variables), or inject them (e.g., using nested scopes).

However, if you want to store and pass the logging instance implicitly in a way that is compatible with await, then you'll need to use the logical call context. I have a blog post describing this approach, which points out the limitations of this approach:

  1. It only works on the full .NET 4.5 framework.
  2. You must use "overwrite" semantics. This generally means storing only immutable data.

With this in mind, here's some code that should work for your needs:

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;
    }
  }
}
like image 39
Stephen Cleary Avatar answered Sep 19 '22 17:09

Stephen Cleary