【问题标题】:Understanding what multiple configureawait(false) do in a single async method了解多个 configureawait(false) 在单个异步方法中的作用
【发布时间】:2018-09-10 14:43:20
【问题描述】:

考虑这段代码:

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) 调用方法 2,相关代码是否会在与段号 2 相同的上下文中运行?

【问题讨论】:

  • 你可以一步步调试这个并回答你自己的问题
  • 真的没有理由使用 ConfigureAwait()。
  • @ErikPhilips,不正确。重新捕获 UI 线程上下文会干扰 UI,有时非常明显,并且会对性能产生严重影响。

标签: c# .net async-await


【解决方案1】:

简化的原始代码

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

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

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

实验

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

实验使用SynchronizationContext,它维护一个简单的string State,在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

所以你有它。运行 the full code listingtruefalse 的各种组合、多个不同的 SynchronizationContext 值以及延迟为 0 的情况对我来说很有启发性。

What does SynchronizationContext do?It's All About the SynchronizationContext的部分内容也值得一读

【讨论】:

  • 很好的答案,为了完整起见,您可以添加:await Task.Delay(0).ConfigureAwait(true);作为第一个动作,要充分说明为什么每个方法只做一次可能还不够。
  • @Stuart 添加附加信息会使答案变得冗长和复杂。
【解决方案2】:

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

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

如果等待 Method1Async(),它会设法从原始上下文中分离。该方法的其余部分将在线程池上下文中执行,以及对同一线程池上下文中的 method2 的调用。第二个问题的答案是肯定的。

【讨论】:

  • 代码 cmets 说,“为简单起见,这不会立即完成。”所以我们可以假设这些任务还没有完成。
  • 另外,当您说 method2 将“在同一个线程池上下文中”时,您的意思并不清楚。
【解决方案3】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-06-21
    • 2015-01-22
    • 2021-07-22
    • 2019-06-14
    • 2019-09-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多