使用ThreadStatic变量与异步/等待(using ThreadStatic variable

2019-07-01 17:31发布

随着新的异步/等待在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.SetDataCallContext.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 ,所以也许同样的事情与使用在这里发生的asyncawait关键字?

考虑到随着使用异步电动机/等待关键字,什么是存储回调线程上恢复了与特定的执行线程可以(自动!)相关数据的最佳方式是什么?

谢谢,

Answer 1:

可以使用CallContext.LogicalSetDataCallContext.LogicalGetData ,但我建议你不这样做,因为他们不支持任何形式的“克隆”,当您使用简单的并行性(的Task.WhenAny / Task.WhenAll )。

我开了一个UserVoice的要求更完整的async兼容的“背景”,更详细地解释一个MSDN论坛帖子 。 它似乎没有可能建立一个自己。 乔恩斯基特有一个良好的博客条目上的主题。

所以,我建议你使用的说法,拉姆达关闭,或本地实例(成员this ),如马克描述。

是的, OperationContext.Current不会保留await秒。

更新:.NET 4.5确实支持Logical[Get|Set]Dataasync代码。 细节上我的博客 。



Answer 2:

基本上,我想强调的是:不这样做。 [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;
}


Answer 3:

获得一个任务延续到相同的线程上执行需要同步提供者。 这是一个昂贵的一句话,简单的诊断是看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基本上是线程安全的。 不与控制台的问题,它是线程安全的。



Answer 4:

对此看看线程

在标有ThreadStaticAttribute领域,初始化只发生一次,在静态构造函数。 在你的代码时,ID为11的新的线程创建一个新的密码字段将被创建,但它是空/空。 当返回到“开始”的任务等待调用任务将完成上线11(为您的打印输出显示)之后,因此字符串为空。

你可以通过存储在里面的“开始”局部域的秘密只是打电话困之前解决你的问题,然后再还原从本地外地秘密从沉睡回国后。 你也可以做到这一点在沉睡你打电话之前“等待Task.Delay(1000);” 实际上导致线程切换。



文章来源: using ThreadStatic variables with async/await