禁用所有库代码中的捕获上下文,ConfigureAwait(false)

在使用await ,默认情况下会捕获SynchronizationContext (如果存在),并使用该上下文执行await (continuation blocks)之后的代码块(这会导致线程上下文切换)。

public async Task DoSomethingAsync()
{
     // We are on a thread that has a SynchronizationContext here.

     await DoSomethingElseAsync();

     // We are back on the same thread as before here 
     //(well sometimes, depending on how the captured SynchronizationContext is implemented)
}

虽然这个默认值在你想在异步操作完成后回到UI线程的UI上下文中是有意义的,但作为大多数其他场景的默认值,这似乎没有任何意义。 对于内部库代码来说,这当然没有意义,因为

  • 它伴随着不必要的线程上下文切换的开销。
  • 意外产生死锁很容易(如此处或此处所述)。
  • 在我看来,微软已经决定了错误的默认设置。

    现在我的问题:

    是否有任何其他(最好是更好的)方法来解决这个问题,而不是使用.ConfigureAwait(false)在我的代码中混淆所有await调用? 这很容易被遗忘,并使得代码的可读性降低。

    更新:可能只需要调用await Task.Yield().ConfigureAwait(false); 在每个方法的开始? 如果这可以保证我将在没有SynchronizationContext的线程上继续执行,则所有后续的await调用都不会捕获任何上下文。


    首先, await Task.Yield().ConfigureAwait(false)将不起作用,因为Yield不返回Task 。 还有其他跳转到池线程的方式,但不建议使用它们,请选中“为什么”从“Async CTP / Release”中删除“SwitchTo”?

    如果你仍然想这样做,这里有一个很好的诀窍,利用了如果在原始线程上有同步上下文, ConfigureAwait(false)会将继续推送到池线程的事实,即使这里没有异步:

    static Task SwitchAsync()
    {
        if (SynchronizationContext.Current == null)
            return Task.FromResult(false); // optimize
    
        var tcs = new TaskCompletionSource<bool>();
        Func<Task> yield = async () =>
            await tcs.Task.ConfigureAwait(false);
        var task = yield();
        tcs.SetResult(false);
        return task;
    }
    
    // ...
    
    public async Task DoSomethingAsync()
    {
        // We are on a thread that has a SynchronizationContext here.
        await SwitchAsync().ConfigureAwait(false); 
    
        // We're on a thread pool thread without a SynchronizationContext 
        await DoSomethingElseAsync(); // no need for ConfigureAwait(false) here
        // ...       
    }
    

    再次,这不是我广泛使用自己的东西。 我对使用ConfigureAwait(false)有类似的担忧。 一个结果是,虽然ConfigureAwait(false)可能不是普遍完美的,使用它与await ,只要你不关心同步上下文是要走的路。 这是.NET源代码本身密切关注的指导原则。

    另一个结果是,如果您担心DoSomethingElseAsync中可能没有正确使用ConfigureAwait(false)第三方代码,只需执行以下操作:

    public async Task DoSomethingAsync()
    {
        // We are on a thread that has a SynchronizationContext here.
        await Task.Run(() => DoSomethingElseAsync()).ConfigureAwait(false);
    
        // We're on a thread pool thread without a SynchronizationContext 
        await DoYetSomethingElseAsync(); // no need for ConfigureAwait(false) here
        // ...       
    }
    

    这将使用接受Func<Task> lambda的Task.Run覆盖,在池线程上运行它并返回一个解包的任务。 你只会在DoSomethingAsync里面第一次await 。 潜在成本与SwitchAsync相同:一个额外的线程切换,但代码更易读并且结构更好。 这是我在工作中使用的方法。


    有没有更好的方法来解决这个问题,比在我的代码中用.ConfigureAwait(false)混淆所有等待的调用? 这很容易被遗忘,并使得代码的可读性降低。

    不是真的。 没有任何“开箱即用”开关,您可以打开以改变该行为。 ConfigureAwaiter Checker ReSharper扩展可以提供帮助。 另一种方法是将您自己的自定义扩展方法或用于切换上下文的SynchronizationContext包装器,或者甚至是自定义的awaiter。

    链接地址: http://www.djcxy.com/p/28589.html

    上一篇: disable capturing context in all library code, ConfigureAwait(false)

    下一篇: Change Chrome tab color with JavaScript or jQuery