您可能会发现 my async
/ await
intro 很有帮助,尤其是讨论“上下文”的部分。
一个常见的例子是下载一些信息,将其解析为数据结构,然后更新 UI,如下所示:
private async Task GetInfoAndUpdateUIAsync()
{
var info = await GetInfoAsync();
UpdateUI(info);
}
private async Task<MyInfo> GetInfoAsync()
{
using (var client = new HttpClient())
{
var httpResponse = await client.GetStringAsync(...);
return MyInfo.Parse(httpResponse);
}
}
演练
您可以GetInfoAndUpdateUIAsync
从 UI 上下文(例如,async void
事件处理程序)调用。
当GetInfoAndUpdateUIAsync
开始执行时,它在 UI 线程上(同步)执行。它做的第一件事就是调用GetInfoAsync
.
GetInfoAsync
也开始在 UI 线程上执行(同步)。它创建一个HttpClient
然后使用它开始从 URL 下载一些数据。当GetInfoAsync
执行它的时候await
,它保存它的状态并且返回一个不完整Task<MyInfo>
的到GetInfoAndUpdateUIAsync
。
GetInfoAndUpdateUIAsync
await
对返回的 执行 an Task<MyInfo>
,这是不完整的。所以它也保存它的状态并返回。这继续备份到原始调用者(例如,async void
事件处理程序)。UI 线程现在可以自由地做其他工作。
当HttpClient
完成下载数据时,它的返回Task<string>
将完成。GetInfoAsync
这将安排在 UI 线程上的延续。
GetInfoAsync
然后将继续在 UI 线程上执行。它在 UI 线程上运行时将响应解析为数据结构 ( MyInfo.Parse
),然后到达方法的末尾,完成Task<MyInfo>
它之前返回的任务。
完成后,它会安排UI 线程Task<MyInfo>
的延续。然后在 UI 线程上调用(同步)。GetInfoAndUpdateUIAsync
GetInfoAndUpdateUIAsync
UpdateUI(info)
结论
这是一个示例,其中方法的各个部分async
将在 UI 线程上(同步)执行,但 UI 线程没有被阻塞。
当async
方法在它们await
a之后Task
继续时,默认情况下它们将在相同的上下文中恢复。如果该“上下文”不为空,则该“上下文”为当前SynchronizationContext
(例如,UI 上下文),否则为当前TaskScheduler
. 您可以通过等待 的结果来覆盖默认行为ConfigureAwait(false)
,这将导致方法继续在线程池线程上运行。
再注
在这个例子中,我们在 UI 线程上做一些实际上不必在 UI 线程上做的事情。特别是,将 HTTP 响应解析为MyInfo
结构。
我们可以GetInfoAsync
通过覆盖默认的上下文捕获来提高效率:
private async Task<MyInfo> GetInfoAsync()
{
using (var client = new HttpClient())
{
var httpResponse = await client.GetStringAsync(...).ConfigureAwait(false);
return MyInfo.Parse(httpResponse);
}
}
现在,当 HTTP 响应进来并GetInfoAsync
继续执行时,它将继续在线程池线程而不是 UI 线程上执行。因此MyInfo.Parse
将在线程池而不是 UI 上执行。解析Task<MyInfo>
完成后,将完成,GetInfoAndUpdateUIAsync
并将继续在 UI 线程上执行。
我们不能对 做同样的事情GetInfoAndUpdateUIAsync
,因为UpdateUI
需要在 UI 线程上运行。
所以这导致了一个最佳实践:ConfigureAwait(false)
在你的“库”async
方法中使用。