当不涉及真正的 I/O 调用时,异步链的所有级别都需要 ConfigureAwait(false) 吗?

Is ConfigureAwait(false) required on all levels of the async chain when no real I/O call is involved?

在 Azure Document Db 客户端 SDK 之上实施可重用适配器类型的库。

库可以 运行 任何地方,不仅在 ASP.NET 核心网络服务中,而且在命令行应用程序中,ASP.NET Web Api 等

在这个库中,所有方法都是异步的,它们只是抽象层,可以更轻松地使用 Document Db 客户端 api。唯一真正的异步调用 - I/O 请求 - 实际上是由 Document Db SDK 中的 api 在最底层完成的。我写的上面的任何代码都只是在内存数据转换、转换中,不涉及实际的 I/O 调用,但它们也是异步的,因为最底层的 Document Db api 是异步的。

我是否仍需要在堆栈中的所有上层代码层上使用 ConfigureAwait(false),或者仅在我自己调用 Document 的代码的最低层上调用 ConfigureAwait(False) 就足够了进行真正 I/O 调用的 Db SDK 方法 ?

await 调用之前的同步代码不同,延续是在不同 call-stack 调用的上下文中执行的。特别是它们被安排作为单独的委托执行,并且它们之间没有 call-stack 关系。因此,为最后一个延续执行指定 ConfigureAwait(false) 将仅对该特定延续生效,其他延续仍将使用其各自的调度配置执行。也就是说,如果您的目标是确保不通过库中的任何延续捕获同步上下文(以防止潜在的死锁或任何其他原因),那么您应该配置所有 await 具有延续 ConfigureAwait(false).

ConfigureAwait(false) 用于防止在初始 SynchronizationContext 上执行。例如,如果您正在使用不需要访问 UI 线程的库(在 WPF 或 WinForms 的情况下),您应该在 all 上使用 ConfigureAwait(false)水平。否则 SynchronizationContext 将被恢复。这是一个简单的 WinForms 应用程序示例:

public partial class Form1 : Form
{
    static readonly HttpClient _hcli = new HttpClient();

    public Form1()
    {
        InitializeComponent();
    }

    private static string log;
    private async void button1_Click(object sender, EventArgs e)
    {
        log = "";
        await M3();
        MessageBox.Show(log);
    }

    static async Task<string> M3()
    {
        LogBefore(nameof(M3));
        var str = await M2();
        LogAfter(nameof(M3));
        return str;
    }

    static async Task<string> M2()
    {
        LogBefore(nameof(M2));
        var str = await M1();
        LogAfter(nameof(M2));
        return str;
    }

    static async Task<string> M1()
    {
        LogBefore(nameof(M1));
        var str = await _hcli.GetStringAsync("http://mtkachenko.me").ConfigureAwait(false);
        LogAfter(nameof(M1));
        return str;
    }

    static void LogBefore(string method)
    {
        log += $"before {method} {Thread.CurrentThread.ManagedThreadId} {SynchronizationContext.Current == null}, ";
    }

    static void LogAfter(string method)
    {
        log += $"after {method} {Thread.CurrentThread.ManagedThreadId} {SynchronizationContext.Current == null}, ";
    }
}

输出:

before M3 1 False
before M2 1 False
before M1 1 False
after M1 12 True //sync.context skipped because of .ConfigureAwait(false)
after M2 1 False //sync.context restored
after M3 1 False //sync.context restored