.NET异步TAP方法是否允许非异步?

时间:2012-08-12 12:29:48

标签: .net asynchronous async-await

Stephen Toub在他的文章“基于任务的异步模式”中描述了实现基于任务的异步模式(TAP)的异步方法甚至可以使用特定的线程(如UI线程)来执行其代码。这涉及允许异步方法使用调用线程。在这种情况下,异步方法实际上不是异步并阻塞调用线程。真的允许吗?创建一个阻止调用线程的异步方法是没有意义的,或者是这样吗?

以下是斯蒂芬的文章的相关条款:

  

目标环境

     

由TAP方法实现决定异步执行的发生位置。 TAP方法的开发人员可以选择在ThreadPool上执行工作负载,可以选择使用异步I / O实现它,因此不会被绑定到大多数操作执行的线程,可以选择运行在需要的特定线程上,例如UI线程,或任何数量的其他潜在上下文。甚至可能是TAP方法没有执行执行的情况,返回一个Task,它只是表示系统中其他地方出现的条件(例如代表Task<TData>的{​​{1}}到达排队的数据结构)。

3 个答案:

答案 0 :(得分:1)

该方法(或其中的一部分)可能需要在UI线程上运行这一事实并不意味着它必须阻止,无论您是否从UI线程调用它。

想象一下,您有一个方法Task DownloadAndShowData(),它会异步下载一些数据,然后在UI中显示它。要在UI上显示下载的数据,需要在UI线程上执行一些代码。你可以实现这样的东西:

async Task DownloadAndShowData()
{
    var data = await DownloadData();
    await uiSchedulerFactory.StartNew(() => ShowData(data));
}

此处,uiSchedulerFactoryTaskFactory,它在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