Stephen Toub在他的文章“基于任务的异步模式”中描述了实现基于任务的异步模式(TAP)的异步方法甚至可以使用特定的线程(如UI线程)来执行其代码。这涉及允许异步方法使用调用线程。在这种情况下,异步方法实际上不是异步并阻塞调用线程。真的允许吗?创建一个阻止调用线程的异步方法是没有意义的,或者是这样吗?
以下是斯蒂芬的文章的相关条款:
目标环境
由TAP方法实现决定异步执行的发生位置。 TAP方法的开发人员可以选择在
ThreadPool
上执行工作负载,可以选择使用异步I / O实现它,因此不会被绑定到大多数操作执行的线程,可以选择运行在需要的特定线程上,例如UI线程,或任何数量的其他潜在上下文。甚至可能是TAP方法没有执行执行的情况,返回一个Task
,它只是表示系统中其他地方出现的条件(例如代表Task<TData>
的{{1}}到达排队的数据结构)。
答案 0 :(得分:1)
该方法(或其中的一部分)可能需要在UI线程上运行这一事实并不意味着它必须阻止,无论您是否从UI线程调用它。
想象一下,您有一个方法Task DownloadAndShowData()
,它会异步下载一些数据,然后在UI中显示它。要在UI上显示下载的数据,需要在UI线程上执行一些代码。你可以实现这样的东西:
async Task DownloadAndShowData()
{
var data = await DownloadData();
await uiSchedulerFactory.StartNew(() => ShowData(data));
}
此处,uiSchedulerFactory
是TaskFactory
,它在UI线程上执行代码(使用SynchrnonizationContext
)。
在此代码中,只有在使用UI线程的UI中显示数据后,才会完成返回的Task
。但该方法本身并没有阻止。如果您有以下代码:
await DownloadAndShowData();
// some more code
在UI线程上执行,会发生的是当达到await
时,当前方法被“暂停”,并释放UI线程。下载完成后,在UI线程上执行上面的ShowData()
(它可以执行,没有任何阻塞线程)。完成此操作后,调用方法将“取消暂停”并执行// some more code
。
总而言之,异步方法在UI线程上执行一些代码(也可能是调用代码正在执行的位置),但async方法不会阻塞。
答案 1 :(得分:0)
调用await
返回Task
对象的方法只是委托给该任务对象以开始调用它的任务。从该任务对象的调用者的角度来看,该任务对象可能已经或可能尚未被调用。如果它已被调用,它可以在await
到达它的时间内完成,在这种情况下,从等待的角度来看,任务同步执行。
这是一个例子。另一个是Task
知道它需要做什么;如果它需要做的事情可以更快地同步完成,它将简单地同步完成。一个示例可以是从远程源接收数据。许多接收数据的方法都实现了TAP,这是因为数据是由远程源发送的,并且是由您的计算机在后台接收的。调用一个“异步”接收字节的方法可能已经有了该字节,同步返回字节会更容易。如果尚未收到任何字节,则该方法将异步运行,等待远程源发送一个字节。收到该字节后,它将通过await
之后的延续或代码异步通知调用者。
答案 2 :(得分:-1)
您可能会找到我的async
/await
intro helpful,特别是讨论“背景”的部分。
一个常见的例子是下载一些信息,将其解析为数据结构,然后更新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);
}
}
<强>操作实例强>
您可以从UI上下文中调用GetInfoAndUpdateUIAsync
(例如,async void
事件处理程序。)
当GetInfoAndUpdateUIAsync
开始执行时,它会在UI线程上执行(同步)。它做的第一件事是致电GetInfoAsync
。
GetInfoAsync
也开始在UI线程上执行(同步)。它创建一个HttpClient
,然后使用它开始从URL下载一些数据。当GetInfoAsync
执行其await
时,会保存其状态并将不完整的Task<MyInfo>
返回给GetInfoAndUpdateUIAsync
。
GetInfoAndUpdateUIAsync
在返回的await
上执行Task<MyInfo>
,但未完成。因此它还保存其状态并返回。这将继续回到原始调用者(例如,async void
事件处理程序)。 UI线程现在可以自由地完成其他工作。
当HttpClient
完成下载数据后,其返回的Task<string>
将完成。这将安排在UI线程上继续GetInfoAsync
。
GetInfoAsync
将继续在UI线程上执行。它在UI线程上运行时将响应解析为数据结构(MyInfo.Parse
),然后到达方法的末尾,完成它之前返回的Task<MyInfo>
任务。
当Task<MyInfo>
完成时,它会安排GetInfoAndUpdateUIAsync
到UI线程的继续。 GetInfoAndUpdateUIAsync
然后在UI线程上调用UpdateUI(info)
(同步)。
<强>结论强>
这是一个示例,其中async
方法的各个部分将在UI线程上执行(同步),但UI线程不是阻止。
当async
方法在await
一个Task
之后继续时,默认情况下它们将在相同的上下文中恢复。如果它不是null,则此“上下文”是当前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
。