343

我试图以最简单的形式理解异步等待。为了这个例子,我想创建一个非常简单的方法,将两个数字相加,当然,这根本不需要处理时间,只是在这里制定一个例子。

示例 1

private async Task DoWork1Async()
{
    int result = 1 + 2;
}

示例 2

private async Task DoWork2Async()
{
    Task.Run( () =>
    {
        int result = 1 + 2;
    });
}

如果我等待DoWork1Async(),代码是同步运行还是异步运行?

我是否需要包装同步代码Task.Run以使方法可等待和异步,以免阻塞 UI 线程?

我试图弄清楚我的方法是 aTask还是返回Task<T>我是否需要包装代码Task.Run以使其异步。

愚蠢的问题我敢肯定,但我在网上看到一些例子,人们正在等待其中没有任何 async 并且没有包裹在Task.Runor中的代码StartNew

4

3 回答 3

651

首先,让我们澄清一些术语:“异步”( async) 意味着它可以在调用线程开始之前将控制权交还给调用线程。在一种async方法中,那些“屈服”点是await表达式。

这与术语“异步”非常不同,因为 MSDN 文档多年来(误用)来表示“在后台线程上执行”。

进一步混淆问题,async与“等待”有很大不同;有些async方法的返回类型不是可等待的,许多方法返回的可等待类型不是async.

关于他们不是什么已经足够了;它们是这样的

  • async关键字允许异步方法(即,它允许await表达式)。async方法可能返回Task, Task<T>, 或(如果必须的话)void
  • 任何遵循某种模式的类型都可以等待。最常见的等待类型是TaskTask<T>

因此,如果我们将您的问题重新表述为“如何以可等待的方式在后台线程上Task.Run运行操作”,答案是使用:

private Task<int> DoWorkAsync() // No async because the method does not need await
{
  return Task.Run(() =>
  {
    return 1 + 2;
  });
}

(但这种模式是一种糟糕的方法;见下文)。

但是,如果您的问题是“我如何创建一个async可以返回给其调用者而不是阻塞的方法”,那么答案是声明该方法asyncawait用于其“屈服”点:

private async Task<int> GetWebPageHtmlSizeAsync()
{
  var client = new HttpClient();
  var html = await client.GetAsync("http://www.example.com/");
  return html.Length;
}

因此,事物的基本模式是让代码在其表达式async中依赖于“等待对象” 。await这些“等待”可以是其他async方法,也可以只是返回等待的常规方法。Task返回/的常规方法Task<T> 用于Task.Run在后台线程上执行代码,或者(更常见)它们可以使用TaskCompletionSource<T>或其快捷方式之一(TaskFactory.FromAsync,Task.FromResult等)。我建议将整个方法包装在Task.Run; 同步方法应该具有同步签名,并且应该由消费者决定是否应该将其包装在 a 中Task.Run

private int DoWork()
{
  return 1 + 2;
}

private void MoreSynchronousProcessing()
{
  // Execute it directly (synchronously), since we are also a synchronous method.
  var result = DoWork();
  ...
}

private async Task DoVariousThingsFromTheUIThreadAsync()
{
  // I have a bunch of async work to do, and I am executed on the UI thread.
  var result = await Task.Run(() => DoWork());
  ...
}

我的博客上有async/await介绍;最后是一些很好的后续资源。MSDN 文档async也非常好。

于 2013-06-15T02:22:46.353 回答
25

使用async装饰方法时要记住的最重要的事情之一是方法内部至少有一个 await运算符。在您的示例中,我将使用TaskCompletionSource将其翻译如下所示。

private Task<int> DoWorkAsync()
{
    //create a task completion source
    //the type of the result value must be the same
    //as the type in the returning Task
    TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
    Task.Run(() =>
    {
        int result = 1 + 2;
        //set the result to TaskCompletionSource
        tcs.SetResult(result);
    });
    //return the Task
    return tcs.Task;
}

private async Task DoWork()
{
    int result = await DoWorkAsync();
}
于 2015-09-26T23:05:15.550 回答
14

当您使用 Task.Run 运行方法时,Task 从线程池中获取一个线程来运行该方法。所以从 UI 线程的角度来看,它是“异步的”,因为它不会阻塞 UI 线程。这对于桌面应用程序来说很好,因为您通常不需要很多线程来处理用户交互。

但是,对于 Web 应用程序,每个请求都由一个线程池线程提供服务,因此可以通过保存这些线程来增加活动请求的数量。频繁使用线程池线程来模拟异步操作对于 Web 应用程序是不可扩展的。

真正的异步不一定涉及使用线程进行 I/O 操作,例如文件/数据库访问等。您可以阅读本文以了解为什么 I/O 操作不需要线程。http://blog.stephencleary.com/2013/11/there-is-no-thread.html

在您的简单示例中,这是一个纯 CPU 密集型计算,因此使用 Task.Run 就可以了。

于 2017-01-25T10:07:42.070 回答