C# 将ThreadStatic变量与async/await一起使用
使用C#中的新async/await关键字,现在会对使用ThreadStatic数据的方式(以及时间)产生影响,因为回调委托是在启动C# 将ThreadStatic变量与async/await一起使用,c#,multithreading,synchronization,async-await,C#,Multithreading,Synchronization,Async Await,使用C#中的新async/await关键字,现在会对使用ThreadStatic数据的方式(以及时间)产生影响,因为回调委托是在启动async操作的另一个线程上执行的。例如,以下简单控制台应用程序: [ThreadStatic] private static string Secret; static void Main(string[] args) { Start().Wait(); Console.ReadKey(); } private static async Tas
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
,所以在这里使用async
和Wait
关键字可能也会发生同样的事情
考虑到使用async/await关键字,存储与可以(自动)在回调线程上恢复的特定执行线程相关联的数据的最佳方法是什么
谢谢,基本上,我要强调的是:不要那样做<代码>[ThreadStatic]永远不会很好地处理线程之间跳转的代码 但你不必这么做。一个
任务
已经带有状态-事实上,它可以通过两种不同的方式完成:
- 有一个显式的state对象,它可以保存您需要的所有内容
- lambdas/anon方法可以在状态上形成闭包
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;
}
[编译生成]
私有结构d__0:IAsyncStateMachine
{
//…这里没有显示更多内容
公共字符串5__1;
}
要在同一线程上执行任务延续,需要同步提供程序。这是一个昂贵的词,简单的诊断是通过查看调试器中System.Threading.SynchronizationContext.Current的值
在控制台模式应用程序中,该值将为空。没有提供程序可以使代码在控制台模式应用程序中的特定线程上运行。只有Winforms或WPF应用程序或ASP.NET应用程序将具有提供程序。而且只在他们的主线上
这些应用程序的主线程做了一些非常特殊的事情,它们有一个调度程序循环(也称为消息循环或消息泵)。它实现了问题的一般解决方案。正是dispatcher循环允许为线程处理一些要执行的工作。这样一点工作将是等待表达后的任务延续。该位将在调度程序线程上运行
WindowsFormsSynchronizationContext是Winforms应用程序的同步提供程序。它使用Control.Begin/Invoke()来分派请求。对于WPF,它是DispatcherSynchronizationContext类,它使用Dispatcher.Begin/Invoke()来调度请求。对于ASP.NET,它是AspNetSynchronizationContext类,它使用不可见的内部管道。它们在初始化时创建各自提供程序的实例,并将其分配给SynchronizationContext.Current
对于控制台模式的应用程序,没有这样的提供程序。主要是因为主线程完全不合适,所以它不使用dispatcher循环。您需要创建自己的,然后还要创建自己的SynchronizationContext派生类。很难做到,你不能再进行像Console.ReadLine()这样的调用了,因为这会完全冻结Windows调用的主线程。您的控制台模式应用程序不再是控制台应用程序,它将开始类似于Winforms应用程序
请注意,这些运行时环境具有同步提供程序是有充分理由的。因为GUI从根本上来说是线程不安全的,所以他们必须拥有一个。控制台没有问题,它是线程安全的。您可以使用CallContext.LogicalSetData
和CallContext.LogicalGetData
,但我建议您不要这样做,因为当您使用简单的并行性(任务.whenay
/任务.WhenAll
)时,它们不支持任何类型的“克隆”
我打开了一个更完整的async
兼容的“上下文”,详细解释见。似乎不可能自己建造一个。乔恩·斯基特对此有自己的见解
因此,我建议您使用参数、lambda闭包或本地实例的成员(this
),如Marc所述
是的,OperationContext.Current
不会在await
s中保留
更新:.NET 4.5在async
代码中支持逻辑[Get | Set]数据。详细信息。看看这个
在标记为ThreadStaticAttribute的字段上,初始化只在静态构造函数中发生一次。在代码中,当创建ID为11的新线程时,将创建一个新的机密字段,但该字段为空/null。当在等待调用之后返回“开始”任务时,任务将在线程11上完成(如打印输出所示),因此字符串为空
您可以通过在调用Sleepy之前将秘密存储在“Start”中的本地字段中,然后在从Sleepy返回后从本地字段恢复秘密来解决问题。您也可以在Sleepy中调用“wait Task.Delay(1000);”之前执行此操作,这实际上会导致线程切换 提供了对维护作用于特定异步代码流的变量的支持
将变量类型更改为AsyncLocal,例如
private static AsyncLocal Secret=new AsyncLocal();
提供以下所需输出:
Started on thread [5]
Secret is [moo moo]
Was on thread [5]
Now on thread [6]
Finished on thread [6]
Secret is [moo moo]
如果是WCF呢?如果将OperationContext
迁移到新线程,我是否应该使用它呢?@theburningm