12

根据许多书籍和博客(包括这里的优秀文章),很明显,当编写一个公开辅助异步方法(即包装器方法)的 dll 库时,通常认为在内部完成实际异步方法的 I/O 任务是一种最佳实践在这样的线程池线程上(为简洁起见,下面显示的伪代码,我HttpClient用作示例)

public Async Task<HttpResponseMessage> MyMethodAsync(..)
{
    ...
    var httpClient = new HttpClient(..);
    var response = await httpClient.PostAsJsonAsync(..).ConfigureAwait(false);
    ...
    return response;
}

这里的关键是使用ConfigureAwait(false)IO 任务完成发生在线程池线程上,而不是在原始线程上下文上,从而潜在地防止死锁。

我的问题是从呼叫者的角度来看的。我对调用者和上述方法调用之间存在多层方法调用的场景特别感兴趣,如下面的示例所示。

CallerA -> Method1Async -> Method2Async -> finally the above MyMethodAsync

仅使用 final 方法就足够了,ConfigureAwait(false)还是应该确保Method1AsyncMethod2Async在内部调用它们的异步方法ConfigureAwait(false)?将它包含在所有这些中间方法中似乎很愚蠢,尤其是如果Method1Async并且Method2Async只是最终调用MyMethodAsync. 有什么想法,请赐教!

使用示例更新 因此,如果我有一个具有以下私有异步方法的库,

private async Task<string> MyPrivateMethodAsync(MyClass myClass)
{
    ...
    return await SomeObject.ReadAsStringAsync().ConfigureAwait(false);
}

我应该确保以下公共重载方法都包括 ConfigureAwait(false) ,如下所示?

public async Task<string> MyMethodAsync(string from)
{
        return await MyPrivateMethodAsync(new (MyClass() { From = from, To = "someDefaultValue"}).ConfigureAwait(false);
}
public async Task<string> MyMethodAsync(string from, string to)
{
        return await MyPrivateMethodAsync(new (MyClass() { From = from, To = to }).ConfigureAwait(false);
}
4

2 回答 2

14

当然不。ConfigureAwait正如它的名字所暗示的那样配置await. 它只影响await与之耦合的。

ConfigureAwait实际上返回一个不同的等待类型,ConfiguredTaskAwaitable而不是Task反过来返回一个不同的等待类型ConfiguredTaskAwaiter而不是TaskAwaiter

如果你想忽略你SynchronizationContext所有的awaits,你必须ConfigureAwait(false)为它们中的每一个使用。

如果您想限制使用,ConfigureAwait(false)可以在最顶部使用我的NoSynchronizationContextScope(请参阅此处):

async Task CallerA()
{
    using (NoSynchronizationContextScope.Enter())
    {
        await Method1Async();
    }
}
于 2015-02-28T00:11:05.133 回答
5

当等待任务时,它会创建一个对应TaskAwaiter的来跟踪该任务,该任务也捕获当前的SynchronizationContext. 任务完成后,等待者在捕获的上下文上运行等待(称为延续)之后的代码。

您可以通过调用 来防止这种情况ConfigureAwait(false),它会创建一种不同类型的 awiatable ( ConfiguredTaskAwaitable) 及其对应的 awaiter ( ConfiguredTaskAwaitable.ConfiguredTaskAwaiter),它不会在捕获的上下文上运行延续。

关键是,对于每个await,都创建了一个不同的 awaiter 实例,它不是在方法或程序中的所有 awaitables 之间共享的东西。因此,最好ConfigureAwait(false)为每个await声明调用。

您可以在此处查看等待者的源代码。

于 2015-02-28T00:15:01.197 回答