问题
How can something like a ThreadStatic be used in a TPL Task? My understanding ("Wrox Professional Parallel Programming with C#", p74) is that a Task can switch from one thread to another during execution.
What I want to do?
I want to maintain a session id inside a static class so I don't need to pass this id to all of my methods. My library has methods like login(id)
, logout(id)
and many methods which operate on credentials associated with this id. But I don't want to pass this id to every method. I can make sure my library is called within different thread for different sessions. So saving the id inside login()
in a ThreadStatic
variable will work.
Now I want to use TPL Tasks which are created for me by a ThreadPool
. I can pass my session id to the Task, but if I store this id inside a ThreadStatic
variable it will not survive if my Task switches threads.
回答1:
TPL and .Net 4.5's async flow the ExecutionContext
, which means you can use CallContext.LogicalSetData(string, object)
and CallContext.GetLogicalData(string)
in much the same way you would use ThreadStatic
. It does incur a significant performance penalty, however. This has been exposed in .Net 4.6 and up (including .Net Standard 1.3 and up) with the AsyncLocal<> wrapper.
See Async Causality Chain Tracking, How to include own data in ExecutionContext, and ExecutionContext vs SynchronizationContext for a deeper dive.
Example use:
class Program
{
static async void Main(string[] args)
{
Logger.Current = new Logger("Test Printer");
Logger.Current.Print("hello from main");
await Task.Run(() => Logger.Current.Print($"hello from thread {Thread.CurrentThread.ManagedThreadId}"));
await Task.Run(() => Logger.Current.Print($"hello from thread {Thread.CurrentThread.ManagedThreadId}"));
}
}
class Logger
{
private string LogName;
public Logger(string logName)
{
if (logName == null)
throw new InvalidOperationException();
this.LogName = logName;
}
public void Print(string text)
{
Console.WriteLine(LogName + ": " + text);
}
private static AsyncLocal<Logger> _logger = new AsyncLocal<Logger>();
public static Logger Current
{
get => _logger.Value;
set => _logger.Value = value;
}
}
}
Prints:
Test Printer: hello from main Test Printer: hello from thread 11 Test Printer: hello from thread 10
回答2:
I'd say avoid the thread static unless you've some guarantee about which tasks run on which threads. Just pass it. Your intent will be clearer.
回答3:
You are correct, thread-static is not suitable for Tasks.
Better overcome your problems with passing the parameter. It is a lot clearer and cleaner to just pass it. A little bit of typing will get you lots more readability and thread-safety.
来源:https://stackoverflow.com/questions/6896652/threadstatic-for-tpl-task