在 Azure 文档 Db 客户端 SDK 之上实现可重用的适配器类型的库。
该库可以在任何地方运行,不仅在 ASP.NET Core Web服务中,而且在命令行应用程序,ASP.NET Web API等中。
在此库中,所有方法都是异步的,它们只是抽象层,可以更轻松地使用文档 Db 客户端 api。唯一真正的异步调用 - I/O 请求 - 实际上是由文档 Db SDK 中的 API 在最低层完成的。我上面写的任何代码都只是在内存数据转换、转换中,不涉及实际的 I/O 调用,但它们也是异步的,因为最低层的文档 Db API 是异步的。
我是否仍然需要在堆栈中的所有上层代码上使用ConfigureAwait(false)
,或者这是否足以仅在我自己的代码的最低层上调用ConfigureAwait(False)
,该代码调用进行实际 I/O 调用的文档 Db SDK 的方法?
与await
调用之前的同步代码不同,延续在不同的调用堆栈调用的上下文中执行。特别是,它们被安排为作为单独的委托执行,并且它们之间没有调用堆栈关系。因此,为最后一个延续执行指定ConfigureAwait(false)
将仅对此特定延续生效,其他延续仍将使用其单独的调度配置执行。也就是说,如果您的目标是确保不会通过库中的任何延续来捕获同步上下文(以防止潜在的死锁或任何其他原因(,则应配置所有与 ConfigureAwait(false)
具有延续await
调用。
ConfigureAwait(false)
用于防止在初始SynchronizationContext
执行。例如,如果您正在使用不需要访问 UI 线程的库(在 WPF 或 WinForms 的情况下(,则应在所有级别上使用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