0

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

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

目标环境

由 TAP 方法的实现来确定异步执行发生的位置。TAP 方法的开发人员可以选择在ThreadPool.需要,例如 UI 线程,或任何数量的其他潜在上下文。甚至可能出现 TAP 方法没有执行执行的情况,返回一个Task简单地表示系统中其他地方出现条件的 a(例如Task<TData>,表示TData到达排队数据结构的 a)。

4

3 回答 3

1

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

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

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

在这里,uiSchedulerFactory是一个TaskFactory在 UI 线程上执行代码的(使用SynchrnonizationContext)。

在这段代码中,Task只有在数据显示在 UI 中后才完成返回,它使用了 UI 线程。但是该方法本身不会阻塞。如果你有这样的代码:

await DownloadAndShowData();
// some more code

在 UI 线程上执行,当await到达时,当前方法“暂停”,UI 线程被释放。下载完成后,ShowData()从上面在 UI 线程上执行(它可以执行,没有任何东西阻塞线程)。完成后,调用方法“取消暂停”并// some more code执行。

总而言之,异步方法在 UI 线程(也可能是调用代码正在执行的位置)上执行一些代码,但异步方法不会阻塞。

于 2012-08-12T13:13:08.420 回答
0

await对返回对象的方法的调用Task只是委托给该任务对象以开始对其任务的调用。从该任务对象的调用者的角度来看,该任务对象可能已经或可能尚未被调用。如果它已经被调用,它可能会在await到达它的时候完成,在这种情况下,从 await 的代码来看,任务是同步执行的。

这是一个例子。另一个是Task知道它需要做什么;如果它需要做的事情可以更快地同步完成,它将简单地同步完成。一个示例可能是从远程源接收数据。许多接收数据的方法都实现了 TAP,这是因为数据是由远程源发送的,并且正在由您的计算机在后台接收。调用“异步”接收字节的方法可能已经拥有该字节,并且同步返回该字节会更容易。如果尚未接收到任何字节,则该方法将异步运行,等待远程源发送一个字节。当接收到该字节时,它将通过延续或await.

于 2012-08-12T16:19:31.173 回答
-1

您可能会发现 my async/ awaitintro 很有帮助,尤其是讨论“上下文”的部分。

一个常见的例子是下载一些信息,将其解析为数据结构,然后更新 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

GetInfoAndUpdateUIAsyncawait对返回的 执行 an Task<MyInfo>,这是不完整的。所以它也保存它的状态并返回。这继续备份到原始调用者(例如,async void事件处理程序)。UI 线程现在可以自由地做其他工作。

HttpClient完成下载数据时,它的返回Task<string>将完成。GetInfoAsync这将安排在 UI 线程上的延续。

GetInfoAsync然后将继续在 UI 线程上执行。它在 UI 线程上运行时将响应解析为数据结构 ( MyInfo.Parse),然后到达方法的末尾,完成Task<MyInfo>它之前返回的任务。

完成后,它会安排UI 线程Task<MyInfo>的延续。然后在 UI 线程上调用(同步)。GetInfoAndUpdateUIAsyncGetInfoAndUpdateUIAsyncUpdateUI(info)

结论

这是一个示例,其中方法的各个部分async将在 UI 线程上(同步)执行,但 UI 线程没有被阻塞

async方法在它们awaita之后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方法中使用。

于 2012-08-12T15:19:09.577 回答