当不涉及真正的 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
在 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