了解多个configureawait(false)在单个异步方法中执行的操作

时间:2018-03-31 23:19:05

标签: c# .net async-await

考虑以下代码:

public async Task SomeMethodAsync(){
    //1. code here executes on the original context

    //for simplicity sake, this doesn't complete instantly 
    var result1 = await Method1Async().ConfigureAwait(false);

    //2. code here doesn't executes in the original context 

现在我的问题是,如果在上面的方法中有另一个异步方法的调用:

    //again for simplicity sake, this doesn't complete instantly 
    var result2 = await Method2();
    //3. code in question is here
}

有问题的代码是在原始上下文中运行还是在另一个上下文中运行(可能是来自线程池或其他上下文的线程)?

另外,如果使用ConfigureAwait(false)调用method2,那么相关代码是否会在与段号2相同的上下文中运行?

3 个答案:

答案 0 :(得分:7)

简化原始代码

//for simplicity sake, this doesn't complete instantly 
var result1 = await Method1Async().ConfigureAwait(false);

//again for simplicity sake, this doesn't complete instantly 
var result2 = await Method2();

//code in question is here

答案

假设你的异步方法没有立即完成,这里有两个简短的答案。

  

有问题的代码是在原始上下文中运行还是在另一个上下文中运行(可能是来自线程池或其他上下文的线程)?

有问题的代码将具有空SynchronizationContext.Current值,因此将以默认SynchronizationContext运行。

关于相关代码运行的线程:SynchronizationContext做出决定。代码被发布/发送到SynchronizationContextSynchronizationContext又将操作转发到特定的计算资源,例如特定的线程,特定的CPU核心或其他东西。在默认SynchronizationContext的情况下,线程的选择取决于托管应用程序的内容(例如,控制台应用程序与ASP.NET应用程序)。在非默认ConfigureAwait(false)的情况下,计算资源的选择取决于实施者的心血来潮:它可以在网络共享上运行。

  

另外,如果使用ConfigureAwait(false)调用method2,那么相关代码是否会在与段号2相同的上下文中运行?

如果method2有SynchronizationContext,那么相关代码也会以默认false运行。换句话说,当我们使用SynchronizationContext时,任务不再尝试在捕获的上下文中继续。

实验

这是一个可以回答你们两个问题的实验(the full listing is here)。

该实验使用维护简单string State的{​​{1}},在Post期间将其自身重置为当前上下文,并覆盖ToString()以输出其State值。

public class MySyncContext : SynchronizationContext
{
    public string State { get; set; }

    public override void Post(SendOrPostCallback callback, object state)
    {
        base.Post(s => { 
            SynchronizationContext.SetSynchronizationContext(this);
            callback(s);
        }, state);
    }

    public override string ToString() => State;
}

它是什么让我们看看代码是否在原始上下文中运行。

所以,让我们回忆一下你问的问题:

  

有问题的代码是在原始上下文中运行还是在另一个上下文中运行(可能是来自线程池或其他上下文的线程)?

要回答这个问题,我们有一个接近您的设置的实验。它首先将原始SynchronizationContext设置为已知状态,然后等待两个异步方法,在第一个方法上使用ConfigureAwait(false),记录当前的SynchronizationContext

static async Task Run()
{
    var syncContext = new MySyncContext { State = "The Original Context" };
    SynchronizationContext.SetSynchronizationContext(syncContext);

    Console.WriteLine("Before:" + SynchronizationContext.Current);

    await Task.Delay(1000).ConfigureAwait(false);
    Console.WriteLine("After Result1:" + SynchronizationContext.Current);

    await Task.Delay(1000);
    Console.WriteLine("After Result2:" + SynchronizationContext.Current);
}

您想知道在第二种方法之后运行的代码是否会在原始上下文中运行。输出答案。第一种和第二种异步方法都没有将它们的延续发布到原始上下文中。

上面的代码ConfigureAwait(false)输出:

Before:The Original Context
After Result1:                       
After Result2:               

如果我们将上面的代码更改为ConfigureAwait(true),则两个方法都在原始上下文中运行它们的连续,输出为:

Before:The Original Context        
After Result1:The Original Context   
After Result2:The Original Context

所以你有它。通过truefalse的各种组合运行the full code listing,使用多个不同的SynchronizationContext值,延迟为0,看看会发生什么,这对我很有启发。

还值得阅读What does SynchronizationContext do?It's All About the SynchronizationContext

的部分内容

答案 1 :(得分:0)

ConfigureAwait(false)基本上抛弃了与Task关联的上下文。因此,当该任务调用其继续时,延续也将没有上下文。由于第二个异步方法是从第一个异步方法的延续中调用的,因此它的任务也没有上下文。在没有上下文的任务上调用ConfigureAwait(false)无效。

答案 2 :(得分:0)

如果在方法中的某个位置使用ConfigureAwait,建议在方法中使用每个Await。原因是只有在等待不完整的任务时才会捕获上下文。如果任务已经完成,则不会捕获上下文。还要记住,硬件和网络之类的东西在执行任务所需的时间中起作用。

第一个问题的答案取决于这些事情。如果Method1Async()在没有等待的情况下完成,则代码为no。 2仍然可以在原始上下文中执行,因此代码为no。 3

等待提供Method1Async(),它设法从原始上下文中分离。该方法的其余部分将在线程池上下文中执行,同时在同一线程池上下文中调用method2。回答你的第二个问题是肯定的。