the*_*onk 35 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 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);
}
Run Code Online (Sandbox Code Playgroud)
将输出以下内容:
Started on thread [9]
Secret is [moo moo]
Was on thread [9]
Now on thread [11]
Finished on thread [11]
Secret is []
Run Code Online (Sandbox Code Playgroud)
我也尝试用CallContext.SetData
和CallContext.GetData
,得到了相同的行为.
阅读完一些相关问题和主题后:
似乎像ASP.Net这样的框架显式地跨越线程迁移HttpContext,但不是CallContext
,所以使用async
和await
关键字可能会发生同样的事情吗?
通过使用async/await关键字,存储与特定执行线程相关联的数据的最佳方法是什么,可以(自动!)在回调线程上恢复?
谢谢,
Ste*_*ary 28
您可以使用CallContext.LogicalSetData
和CallContext.LogicalGetData
,但我建议您不要使用它,因为当您使用简单并行(Task.WhenAny
/ Task.WhenAll
)时它们不支持任何类型的"克隆" .
我打开了一个UserVoice请求,以获得更完整async
兼容的"上下文",在MSDN论坛帖子中有更详细的解释.我们似乎不可能自己建造一个.Jon Skeet 在这个主题上有一篇很好的博客文章.
因此,我建议您使用参数,lambda闭包或本地实例(this
)的成员,如Marc所述.
是的,OperationContext.Current
并没有保留在await
s.
更新: .NET 4.5不支持Logical[Get|Set]Data
的async
代码.我博客上的详细信息.
Mar*_*ell 10
基本上,我要强调:不要那样做.[ThreadStatic]
永远不会很好地使用在线程之间跳转的代码.
但你没必要.一个Task
已经携带的状态 - 事实上,它可以通过两种不同的方式实现:
此外,编译器还可以执行此处所需的所有操作:
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);
}
Run Code Online (Sandbox Code Playgroud)
没有静态; 没有线程或多个任务的问题.它只是有效.请注意,secret
这里不仅仅是"本地"; 编译器已经处理了一些伏都教,就像它对迭代器块和捕获的变量一样.检查反射器,我得到:
[CompilerGenerated]
private struct <Start>d__0 : IAsyncStateMachine
{
// ... lots more here not shown
public string <secret>5__1;
}
Run Code Online (Sandbox Code Playgroud)
小智 7
AsyncLocal<T>支持维护范围为特定异步代码流的变量。
将变量类型更改为 AsyncLocal,例如,
private static AsyncLocal<string> Secret = new AsyncLocal<string>();
Run Code Online (Sandbox Code Playgroud)
给出以下所需的输出:
Started on thread [5]
Secret is [moo moo]
Was on thread [5]
Now on thread [6]
Finished on thread [6]
Secret is [moo moo]
Run Code Online (Sandbox Code Playgroud)
要使任务继续在同一线程上执行,需要同步提供程序.这是一个昂贵的词,简单的诊断是通过在调试器中查看System.Threading.SynchronizationContext.Current的值.
在控制台模式应用程序中该值将为null.没有提供程序可以在控制台模式应用程序中的特定线程上运行代码.只有Winforms或WPF应用程序或ASP.NET应用程序才有提供程序.而且只在他们的主线上.
这些应用程序的主要线程做了一些非常特殊的事情,它们有一个调度程序循环(也就是消息循环或消息泵).这实现了生产者 - 消费者问题的一般解决方案.这是一个调度程序循环,它允许一个线程执行一些工作.这样一点工作将是await表达式之后的任务延续.那个位将在调度程序线程上运行.
WindowsFormsSynchronizationContext是Winforms应用程序的同步提供程序.它使用Control.Begin/Invoke()来分派请求.对于WPF,它是DispatcherSynchronizationContext类,它使用Dispatcher.Begin/Invoke()来分派请求.对于ASP.NET,它是AspNetSynchronizationContext类,它使用不可见的内部管道.他们在初始化时创建各自提供者的实例,并将其分配给SynchronizationContext.Current
控制台模式应用程序没有这样的提供程序.主要是因为主线程完全不适合,它不使用调度程序循环.您可以创建自己的,然后也创建自己的SynchronizationContext派生类.很难做到,你不能再调用Console.ReadLine(),因为它完全冻结了Windows调用中的主线程.您的控制台模式应用程序停止作为控制台应用程序,它将开始类似于Winforms应用程序.
请注意,这些运行时环境具有同步提供程序是有充分理由的.他们必须拥有一个,因为GUI从根本上说是线程不安全的.控制台没有问题,它是线程安全的.
归档时间: |
|
查看次数: |
11488 次 |
最近记录: |