库中的 ConfigureAwait(false) 是否会丢失调用应用程序的同步上下文?

Can ConfigureAwait(false) in a library lose the synchronization context for the calling application?

我从比我聪明的人那里读过很多次建议,其中很少有注意事项:始终在库代码中使用 ConfigureAwait(false)。所以我相当确定我知道答案,但我想成为 100%。场景是我有一个库,它薄薄地包装了一些其他异步库。

图书馆代码:

public async Task DoThingAsyc() {
    // do some setup
    return await otherLib.DoThingAsync().ConfigureAwait(false);
}

申请代码:

// need to preserve my synchronization context
await myLib.DoThingAync();
// do I have my context here or did my lib lose it?

没有

SynchronizationContext 的捕获发生在 awaitConfigureAwait 配置特定的 await.

如果应用程序调用库的异步方法并等待它,则无论调用内部发生什么,SC 都会被当场捕获。

现在,因为异步方法的同步部分(即第一个 await 之前的部分)是在 return 等待任务之前执行的,所以您可以乱用 SynchronizationContext 在那里,但是 ConfigureAwait 没有那样做。


在您的具体示例中,您似乎是 returning 来自异步方法的 ConfigureAwait 的结果。这不可能发生,因为 ConfigureAwait return 是 ConfiguredTaskAwaitable 结构。但是,如果我们更改方法 return 类型:

public ConfiguredTaskAwaitable DoThingAsyc() 
{
    return otherLib.DoThingAsync().ConfigureAwait(false);
}

那么等待它确实会影响调用代码的等待行为。

示例来自 http://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx

... logically you can think of the following code:

await FooAsync();
RestOfMethod();

as being similar in nature to this:

var t = FooAsync();
var currentContext = SynchronizationContext.Current;
t.ContinueWith(delegate
{
    if (currentContext == null)
        RestOfMethod();
    else
        currentContext.Post(delegate { RestOfMethod(); }, null);
}, TaskScheduler.Current);

这意味着您应该在 await myLib.DoThingAync(); 调用后恢复上下文。

如果在异步调用的逻辑链中使用不一致,ConfigureAwait(false)可能会添加冗余的上下文切换(这通常意味着冗余的线程切换)。这可能发生在存在同步上下文的情况下,当逻辑堆栈上的一些异步调用使用 ConfigureAwait(false) 而有些不使用(更多 )时。

您仍然应该在您的代码中使用 ConfigureAwait(false),但您可能想查看您正在调用的第 3 方代码并减少与以下内容的任何不一致:

public async Task DoThingAsyc() {
    // do some setup
    await Task.Run(() => otherLib.DoThingAsync()).ConfigureAwait(false);
    // do some other stuff
}

这会增加一个额外的线程切换,但可能会阻止许多其他线程切换。

此外,如果您正在创建一个非常薄的包装器,就像您展示的那样,您可能希望像下面那样实现它,根本没有 async/await

public Task DoThingAsyc() {
    // do some setup
    return otherLib.DoThingAsync();
}