如何在C#中跨异步等待模型维护线程上下文?

Ash*_*win 3 c# threadstatic async-await

每次等待完成"选项"时,是否正在使用ThreadStatic并设置上下文?还有另外一种方法吗?

public async void Test()
{
    // This is in Thread 1
    Foo foo = new Foo();
    Context.context = "context1"; // This is ThreadStatic
    string result = await foo.CallAsynx();

    // This is most likely Thread 2
    Context.context = "context1";   // This might be a different thread and so resetting context    
}
Run Code Online (Sandbox Code Playgroud)

如果我不想使用ThreadStatic,现在有另一种方法吗?

Ste*_*ary 15

ThreadStatic,ThreadLocal<T>线程数据槽和CallContext.GetData/或CallContext.SetData不能正常工作async,因为它们是特定于线程的.

最好的选择是:

  1. 将其作为@PauloMorgado建议的参数传递.等价地,您可以将其设置为对象的字段成员(它通过参数隐式传递this); 或者你可以让你的lambdas捕获变量(在下面,编译器会隐式地将它作为参数通过this).
  2. 使用HttpContext.Items(如果您使用的是ASP.NET 4.5).
  3. 使用CallContext.LogicalGetData/ CallContext.LogicalSetData作为@Noseratio建议.您只能在逻辑线程上下文中存储不可变数据; 它只适用于.NET 4.5,并不适用于所有平台(例如,Win8).
  4. async通过为该线程安装"主循环"(例如AsyncContext来自我的AsyncEx库),强制所有延续回到同一个线程.

  • 这里只想提一下,因为.Net 4.6有一个`AsyncLocal <T>`似乎可以解决这个问题 (6认同)
  • 不,它不使用`ThreadStatic`.:)"逻辑调用上下文"是[执行上下文的一部分](http://blogs.msdn.com/b/pfxteam/archive/2012/06/15/executioncontext-vs-synchronizationcontext.aspx)即(当你的代码"流"到其他线程时,由框架复制.在.NET 4.5中,逻辑调用上下文具有写时复制行为,这使其能够按预期的方式运行异步代码. (2认同)

Mar*_*kus 11

只是如果几年后有人有同样的问题并找到这个线程......

有一个新功能叫做

AsyncLocal<T>
Run Code Online (Sandbox Code Playgroud)

https://learn.microsoft.com/en-us/dotnet/api/system.threading.asynclocal-1?view=netcore-3.1

这适用于“async/await”,也适用于:

  • 任务.运行(...)
  • 调度程序.BeginInvoke(...)
  • 新线程(...).Start()

我只是用以下代码测试这三个:

    private void StartTests() {
        Thread.Sleep(1000);
        Task.Run(() => DoWork1());
        Task.Run(() => DoWork2());
    }

    private void DoWork1() {
        ThreadContext.Context.Value = "Work 1";
        Thread.Sleep(5);
        Task.Run(() => PrintContext("1"));
        Thread.Sleep(10);
        Dispatcher.BeginInvoke(new Action(() => PrintContext("1")));
        Thread.Sleep(15);
        var t = new Thread(() => PrintContextT("1"));
        t.Start();
    }

    private void DoWork2() {
        ThreadContext.Context.Value = "Work 2";
        Task.Run(() => PrintContext("2"));
        Thread.Sleep(10);
        Dispatcher.BeginInvoke(new Action(() => PrintContext("2")));
        Thread.Sleep(10);
        var t = new Thread(() => PrintContextT("2"));
        t.Start();
    }

    private void PrintContext(string c) {
        var context = ThreadContext.Context.Value;
        Console.WriteLine("P: " + context + "-" + c);

        Task.Run(() => PrintContext2(c));
    }

    private void PrintContext2(string c) {
        Thread.Sleep(7);
        var context = ThreadContext.Context.Value;
        Console.WriteLine("P2: " + context + "-" + c);
    }

    private void PrintContextT(string c) {
        var context = ThreadContext.Context.Value;
        Console.WriteLine("T: " + context + "-" + c);
    }

    public class ThreadContext {
        public static AsyncLocal<object> Context = new AsyncLocal<object>();
    }
Run Code Online (Sandbox Code Playgroud)

输出:

P:工作2-2

P:工作1-1

P2:工作2-2

P:工作2-2

P2:工作1-1

P:工作1-1

P2:工作2-2

T:工作2-2

P2:工作1-1

T:工作1-1