随着新的异步/等待在C#中,现在还有影响到您使用ThreadStatic数据的方式(当)的关键字,因为回调委托在不同的线程一个执行的async
操作开始。 举例来说,下面这个简单的控制台应用程序:
[ThreadStatic]
private static string Secret;
static void Main(string[] args)
{
Start().Wait();
Console.ReadKey();
}
private static async Task Start()
{
Secret = "moo moo";
Console.WriteLine("Started on thread [{0}]", Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("Secret is [{0}]", Secret);
await Sleepy();
Console.WriteLine("Finished on thread [{0}]", Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("Secret is [{0}]", Secret);
}
private static async Task Sleepy()
{
Console.WriteLine("Was on thread [{0}]", Thread.CurrentThread.ManagedThreadId);
await Task.Delay(1000);
Console.WriteLine("Now on thread [{0}]", Thread.CurrentThread.ManagedThreadId);
}
将输出的东西沿着线:
Started on thread [9]
Secret is [moo moo]
Was on thread [9]
Now on thread [11]
Finished on thread [11]
Secret is []
我也尝试使用CallContext.SetData
和CallContext.GetData
,得到了相同的行为。
阅读一些相关的问题和线程后:
- CallContext中VS ThreadStatic
- http://forum.springframework.net/showthread.php?572-CallContext-vs-ThreadStatic-vs-HttpContext&highlight=LogicalThreadContext
- http://piers7.blogspot.co.uk/2005/11/threadstatic-callcontext-and_02.html
似乎像ASP.Net框架明确地迁移跨线程的HttpContext,而不是CallContext
,所以也许同样的事情与使用在这里发生的async
和await
关键字?
考虑到随着使用异步电动机/等待关键字,什么是存储回调线程上恢复了与特定的执行线程可以(自动!)相关数据的最佳方式是什么?
谢谢,
你可以使用CallContext.LogicalSetData
和CallContext.LogicalGetData
,但我建议你不这样做,因为他们不支持任何形式的“克隆”,当您使用简单的并行性(的Task.WhenAny
/ Task.WhenAll
)。
我开了一个UserVoice的要求更完整的async
兼容的“背景”,更详细地解释一个MSDN论坛帖子 。 它似乎没有可能建立一个自己。 乔恩斯基特有一个良好的博客条目上的主题。
所以,我建议你使用的说法,拉姆达关闭,或本地实例(成员this
),如马克描述。
是的, OperationContext.Current
是不会保留await
秒。
更新:.NET 4.5确实支持Logical[Get|Set]Data
的async
代码。 细节上我的博客 。
基本上,我想强调的是:不这样做。 [ThreadStatic]
是永远不会与线程之间跳转代码发挥很好。
但你不必。 一个Task
已经携带状态-事实上,它可以做两种不同的方式:
- 有一个明确的状态对象,可容纳你需要的一切
- 的lambda /匿名的方法可以形成在封闭状态
此外,编译器,你需要反正这里的一切:
private static async Task Start()
{
string secret = "moo moo";
Console.WriteLine("Started on thread [{0}]",
Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("Secret is [{0}]", secret);
await Sleepy();
Console.WriteLine("Finished on thread [{0}]",
Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("Secret is [{0}]", secret);
}
没有静止状态; 与线程或多个任务没有问题。 这只是工作 。 需要注意的是secret
不只是一个“地方”在这里; 编译器已经工作过一些巫术,喜欢它的迭代器块,抓获变量一样。 检查反射器,我得到:
[CompilerGenerated]
private struct <Start>d__0 : IAsyncStateMachine
{
// ... lots more here not shown
public string <secret>5__1;
}
获得一个任务延续到相同的线程上执行需要同步提供者。 这是一个昂贵的一句话,简单的诊断是看System.Threading.SynchronizationContext.Current在调试器的价值。
该值将在控制台模式应用空 。 没有供应商,可以使在一个控制台模式的应用程序中的特定线程的代码运行。 只有一个WinForms或WPF应用程序或ASP.NET应用程序将有一个供应商。 而且,只有在他们的主线程。
这些应用程序的主线程做一些很特别的,他们有一个调度循环(又名消息循环或消息泵)。 它实现了在一般的解决生产者-消费者问题 。 这是调度循环,使递过一个线程一些工作来执行。 这样的一些工作将是AWAIT表达后的任务延续。 而该位将调度线程上运行。
该WindowsFormsSynchronizationContext是WinForms应用程序同步提供。 它使用Control.Begin /调用()来分派该请求。 为WPF它是DispatcherSynchronizationContext类,它使用Dispatcher.Begin /调用()来分派该请求。 对于ASP.NET它是AspNetSynchronizationContext类,它采用了隐形内部管道。 他们在初始化建立各自的供应商的一个实例,并将其分配给SynchronizationContext.Current
有没有这样的提供商控制台模式的应用程序。 主要是因为主线程是完全不合适的,它不使用调度循环。 你必须创建自己的,那么还可以创建自己的SynchronizationContext派生类。 很难做到,你不能像到Console.ReadLine()的调用了,因为这完全冻结在Windows调用主线程。 您的控制台模式的应用不再是一个控制台应用程序,它会启动类似WinForms应用程序。
请注意,这些运行时环境有一个很好的理由同步提供。 他们必须有一个,因为GUI基本上是线程安全的。 不与控制台的问题,它是线程安全的。
对此看看线程
在标有ThreadStaticAttribute领域,初始化只发生一次,在静态构造函数。 在你的代码时,ID为11的新的线程创建一个新的密码字段将被创建,但它是空/空。 当返回到“开始”的任务等待调用任务将完成上线11(为您的打印输出显示)之后,因此字符串为空。
你可以通过存储在里面的“开始”局部域的秘密只是打电话困之前解决你的问题,然后再还原从本地外地秘密从沉睡回国后。 你也可以做到这一点在沉睡你打电话之前“等待Task.Delay(1000);” 实际上导致线程切换。