您是否必须将Task.Run放入一个使它异步的方法中?


304

我试图以最简单的形式理解异步等待。为了这个示例,我想创建一个非常简单的方法,将两个数字相加,这是完全没有时间的,这只是在此处举例说明而已。

例子1

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

例子2

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

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

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

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

我确定是愚蠢的问题,但我在网上看到的示例中,人们正在等待代码,这些代码中没有异步内容并且没有包装在Task.Runor中StartNew


30
您的第一个摘要不是向您发出任何警告吗?
2013年

Answers:


586

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

这与“异步”一词有很大不同,因为MSDN文档使用(多年来)表示“在后台线程上执行”。

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

足够了他们不是。这里是他们什么

  • 所述async关键字允许异步方法(即,它允许await表达式)。async方法可能会返回TaskTask<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可以让其返回给调用者而不是阻止它的方法”,那么答案就是声明该方法async并将await其用于“屈服”点:

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

因此,事物的基本模式是使async代码依赖于其await表达式中的“ awaitables” 。这些“等待项”可以是其他async方法,也可以是返回等待项的常规方法。常规方法返回Task/ Task<T> 使用Task.Run到在后台线程执行代码,或(更常见),他们可以用TaskCompletionSource<T>或它的快捷方式(之一TaskFactory.FromAsyncTask.FromResult等等)。我建议在中包装整个方法Task.Run。同步方法应具有同步签名,是否应将其包装在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/ awaitintro;最后是一些很好的后续资源。的MSDN文档async也非常出色。


8
@sgnsajgon:是的。async方法必须返回TaskTask<T>voidTask并且Task<T>是等待的;void不是。
Stephen Cleary 2014年

3
实际上,async void将编译一个方法签名,这是一个非常糟糕的主意,因为您松开了异步任务的指针
IEatBagels 2014年

4
@TopinFrassi:是的,它们会编译,但void不是可以期待的。
Stephen Cleary 2014年

4
@ohadinho:不,我在博客中谈论的是整个方法只是一个调用Task.Run(例如DoWorkAsync此答案)。使用Task.Run呼叫从UI上下文的方法是合适的(如DoVariousThingsFromTheUIThreadAsync)。
Stephen Cleary

2
对,就是这样。它是有效的使用Task.Run调用一个方法,但如果有一个Task.Run周围的所有(或几乎所有)方法的代码,那么这是一个反模式的-只要保持这种方法同步和移动Task.Run升了一级。
Stephen Cleary

22

异步装饰方法时要记住的最重要的事情之一 是,方法内部至少有一个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 void DoWork()
{
    int result = await DoWorkAsync();
}

26
为什么使用TaskCompletionSource,而不是仅返回Task.Run()方法返回的任务(并将其主体更改为返回结果)?
具有讽刺意味的

4
只是一个旁注。具有“异步无效”签名的方法通常是不好的做法,并被认为是错误的代码,因为它很容易导致UI死锁。主要的例外是异步事件处理程序。
Jazzeroki '16

12

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

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

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

在您的简单示例中,它是纯CPU限制的计算,因此使用Task.Run很好。


因此,如果必须在Web api控制器中使用同步外部api,则不应将同步调用包装在Task.Run()中?如您所说,这样做将使初始请求线程保持畅通无阻,但是它正在使用另一个池线程来调用外部api。实际上,我认为这仍然是一个好主意,因为这样做可以在理论上使用两个池线程来处理许多请求,例如,一个线程可以处理许多传入的请求,而另一个线程可以为所有这些请求调用外部api?
stt106

我同意,我并不是说您不应该绝对将所有同步调用包装在Task.Run()中。我只是指出潜在的问题。
zheng yu

1
@ stt106 I should NOT wrap the synchronous call in Task.Run()是正确的。如果这样做,您只是在切换线程。也就是说,您要取消阻止初始请求线程,但要从线程池中获取另一个线程,该线程本来可以用来处理另一个请求。唯一的结果是,通话完成时绝对为零增益时上下文切换开销
Saeb Amini
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.