理解C# 5中async/await的上下文

30

我是否正确理解,async/await 本身与并发/并行无关,只是一种续传风格(CPS)实现?真正的线程处理是由 SynchronizationContext 实例执行,而 await 则进行传递/还原操作?

如果我的理解正确,那么我对 SynchronizationContext 有以下问题:
它保证延续将在相同的线程上执行。

但是,有没有保证线程的上下文信息得以保留呢?比如 NameCurrentPrincipalCurrentCultureCurrentUICulture 等等。这取决于框架(ASP.NET、WinForms、WCF、WPF)吗?


4
通常情况下,“SynchronizationContext”并不保证后续操作将在同一线程上执行。某些情况下(如WPF、Winforms),确实会这样,但有些情况下却不会(如ASP.NET)。 - svick
你可以为线程设置默认的文化,方法如下:点击这里 - Mike Fuchs
2个回答

45
我理解您的意思是,async/await本身与并发/并行无关,只是CPS实现,对吗?

嗯,async/await是使用CPS进行重写的,所以你的核心理解是正确的。

关于“并发”和“并行”,我认为它确实可以实现并发;你可以启动多个异步操作,它们都会同时进行。这可以通过Task.WhenAllTask.WhenAny轻松实现。

此外,即使async本身并不意味着“多线程”,Task.Run也可以实现容易的async-兼容多线程。

实际的线程是由await传递/恢复的SynchronizationContext实例执行的吗?

这样考虑:由CPS重写创建的延续必须在某个地方运行。捕获的“异步上下文”可用于调度延续。

顺便提一下:捕获的上下文实际上是SynchronizationContext.Current除非为空,在这种情况下,捕获的上下文是TaskScheduler.Current

另一个重要的注释是:上下文的捕获和恢复实际上取决于“awaiter”对象。所以,默认情况下,如果您await一个Task(或任何其他内置的可等待对象),上下文将被捕获和恢复。但是,如果您awaitConfigureAwait(false)的结果,则不会捕获上下文。同样,如果您await自己的自定义可等待对象,则它不会捕获上下文(除非您编程设置为这样)。

然而,是否有任何保证线程的上下文信息被持久保存?我的意思是名称、CurrentPrincipal、CurrentCulture、CurrentUICulture等。

SynchronizationContextExecutionContext 不同。简单来说,ExecutionContext 总是“流动”,因此 CurrentPrincipal 也会随之流动(如果不这样做,可能会存在安全问题,这就是为什么不流动 ExecutionContext 的 API 总是以 Unsafe 结尾的原因)。

在 UI 应用程序中,文化不会流动,但默认情况下对于所有线程都是相同的。除非你在同一线程上恢复(例如使用 UI SynchronizationContext),否则 Name 绝对不会流动。


如果想进一步了解,请先看我的async/await 教程,然后再查看官方的 async/await FAQ。然后再看一下Stephen Toub 关于 ExecutionContextSynchronizationContext 的博客文章

你还可以阅读我的SynchronizationContext 文章,可能会有所帮助。


2
谢谢。Stephen Toub关于ExecutionContext和SynchronizationContext的博客文章链接对我非常有帮助。 - Scott
2
兄弟,你的异步编程真是无处不在啊。太棒了! - Jess
1
Stephen Toub的帖子链接出现了403错误,但我在这里找到了它:https://devblogs.microsoft.com/pfxteam/executioncontext-vs-synchronizationcontext/。 - Darren Lewis

4
不,async/await关键字与并发有很大关系。 async/await基本上将你的方法代码包装成一个任务和继续操作。要查看编译器使用任务并行库生成的确切翻译,请反汇编一些代码片段。这种async/await用法的翻译与下面的示例“类似”(但不完全相同!)。
async Task<int> TaskOfTResult_MethodAsync()
{
    int hours;
    // . . .
    // Return statement specifies an integer result.
    return hours;
}

// Calls to TaskOfTResult_MethodAsync
Task<int> returnedTaskTResult = TaskOfTResult_MethodAsync();
int intResult = await returnedTaskTResult;
// or, in a single statement
int intResult = await TaskOfTResult_MethodAsync();

这大致转换为:

private int Result()
{
    int hours;
    // . . .
    // Return statement specifies an integer result.
    return hours;
}

在方法外等待返回结果,例如:

int? hours = null;
Task<int> task = null;
task = Task.Factory.StartNew<int>(() => Result());
task.ContnueWith(cont => 
{
    // Some task completion checking...
    hours = task.Result;
}, CancellationToken.None, 
   TaskCreationOptions.None, 
   TaskScheduler.Current);

或者,你可以将TPL代码放入Result方法中。
private int ResultAsync()
{
    int? hours = null;
    Task<int> task = null;
    task = Task.Factory.StartNew<int>(() => 
    {
        int hours;
        // . . .
        // Return statement specifies an integer result.
        return hours;
    }, CancellationToken.None, 
       TaskCreationOptions.None, 
       TaskScheduler.Current);
    try
    {
        return task.Result;
    }
    catch (AggregateException aggEx)
    {
        // Some handler method for the agg exception.
        aggEx.Handle(HandleException); 
    }
}

SynchronizationContext 不能保证 async/await 代码的继续执行将在同一线程上执行。但是,您可以使用 TPL 代码通过 SynchronisationContex 关键字来设置上下文。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接