ThreadLocal and await
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/
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:
- It only works on the full .NET 4.5 framework.
- 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;
}
}
}