你必须把Task.Run放在一个方法来使其异步?

Nea*_*eal 288 c# async-await c#-5.0 .net-4.5

我试图以最简单的形式理解异步等待.我想创建一个非常简单的方法,为了这个例子添加两个数字,被授予,它根本没有处理时间,这只是在这里制定一个例子的问题.

例1

private async Task DoWork1Async()
{
    int result = 1 + 2;
}
Run Code Online (Sandbox Code Playgroud)

例2

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

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

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

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

愚蠢的问题我很确定,但是我在网上看到人们正在等待代码中没有任何异步并且未包含在Task.Run或中的示例StartNew.

Ste*_*ary 559

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

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

进一步混淆这个问题,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;
  });
}
Run Code Online (Sandbox Code Playgroud)

(但这种模式很差;见下文).

但是如果你的问题是"我如何创建一个async可以回复其调用者而不是阻塞"的方法,那么答案就是声明方法async并使用await它的"让步"点:

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

因此,事物的基本模式是让async代码依赖于await表达式中的"等待" .这些"等待"可以是其他async方法或只是常规方法返回等待.常规方法返回Task/ Task<T> 使用Task.Run到在后台线程执行代码,或(更常见),他们可以用TaskCompletionSource<T>或它的快捷方式(之一TaskFactory.FromAsync,Task.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());
  ...
}
Run Code Online (Sandbox Code Playgroud)

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

  • @sgnsajgon:是的.`async`方法必须返回`Task`,`Task <T>`或`void`.`Task`和`Task <T>`是等待的; `void`不是. (7认同)
  • @TopinFrassi:是的,他们会编译,但是`void`是不可能的. (4认同)
  • @ohadinho:不,我在博客文章中谈论的是整个方法只是调用`Task.Run`(如本答案中的`DoWorkAsync`).使用`Task.Run`从*上下文调用*方法是合适的(如`DoVariousThingsFromTheUIThreadAsync`). (4认同)
  • 实际上,一个`async void`方法签名会编译,当你丢失指向异步任务的指针时,这只是一个非常糟糕的主意 (3认同)
  • 对,就是这样.使用`Task.Run`来*调用*方法是有效的,但如果在方法代码的所有(或几乎所有)代码周围都有一个`Task.Run`,那么这就是一个反模式 - 只需保持该方法同步,将`Task.Run`提升一个级别. (2认同)
  • 我对于将第 666 个“+1”添加到此线程并不感到非常自豪。非常感谢这篇文章。 (2认同)

Ron*_*mos 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();
}
Run Code Online (Sandbox Code Playgroud)

  • 为什么使用TaskCompletionSource,而不仅仅返回Task.Run()方法返回的任务(并将其主体更改为返回结果)? (25认同)
  • 只是旁注.具有"异步空白"签名的方法通常是不好的做法,并且被认为是错误的代码,因为它可以很容易地导致UI死锁.主要的例外是异步事件处理程序. (4认同)

zhe*_* yu 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很好.

  • @stt106 “我不应该将同步调用包装在 Task.Run() 中”,这是正确的。如果你这样做,你只是在切换线程。即,您正在解锁初始请求线程,但您正在从线程池中获取另一个线程,该线程本可以用于处理另一个请求。唯一的结果是当调用完成时上下文切换开销绝对为零 (3认同)