ChatGPT解决这个技术问题 Extra ChatGPT

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

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

示例 1

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

示例 2

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

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

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

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

我在网上看到一些示例,人们正在等待其中没有任何异步内容且未包装在 Task.RunStartNew 中的代码。

你的第一个片段不是给你任何警告吗?

S
Stephen Cleary

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

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

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

他们不是什么就够了;它们是这样的:

async 关键字允许使用异步方法(即,它允许等待表达式)。异步方法可能返回 Task、Task 或(如果必须)void。

任何遵循某种模式的类型都可以等待。最常见的等待类型是 Task 和 Task

因此,如果我们将您的问题重新表述为“我怎样才能以一种可等待的方式在后台线程上运行操作”,答案是使用 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.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());
  ...
}

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


@sgnsajgon:是的。 async 方法必须返回 TaskTask<T>voidTaskTask<T> 是可等待的; void 不是。
实际上,async void 方法签名将编译,这只是一个非常糟糕的想法,因为您失去了指向异步任务的指针
@TopinFrassi:是的,它们会编译,但 void 不可等待。
@ohadinho:不,我在博文中所说的是整个方法只是对 Task.Run 的调用(如此答案中的 DoWorkAsync )。使用 Task.Run 从 UI 上下文调用方法是合适的(如 DoVariousThingsFromTheUIThreadAsync)。
对,就是这样。使用 Task.Run invoke 方法是有效的,但如果方法的所有(或几乎所有)代码周围都有一个 Task.Run,那么这是一种反模式 - 只需保持该方法同步并将 Task.Run 上移一级。
B
Beltway

使用 async 装饰方法时要记住的最重要的事情之一是至少有 一个 await<方法中的 /strong> 运算符。在您的示例中,我将使用 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();
}

为什么要使用 TaskCompletionSource,而不是仅仅返回 Task.Run() 方法返回的任务(并将其主体更改为返回结果)?
只是一个旁注。具有“async void”签名的方法通常是不好的做法,并且被认为是错误的代码,因为它很容易导致 UI 死锁。主要的例外是异步事件处理程序。
不知道为什么 async void 被认为是“不好的做法”,有很多应用程序你会使用它,基本上任何时候你需要做一些你不关心的事情什么时候结束。
C
Craig.Feied

当您使用 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 就可以了。


因此,如果我必须在 Web api 控制器中使用同步外部 api,我不应该将同步调用包装在 Task.Run() 中吗?正如您所说,这样做将使初始请求线程保持畅通,但它使用另一个池线程来调用外部 api。事实上,我认为这仍然是一个好主意,因为这样做理论上可以使用两个池线程来处理许多请求,例如一个线程可以处理许多传入请求,另一个线程可以为所有这些请求调用外部 api?
我同意。我并不是说您绝对不应该将所有同步调用都包含在 Task.Run() 中。我只是指出潜在的问题。
@stt106 I should NOT wrap the synchronous call in Task.Run() 是正确的。如果你这样做,你只是在切换线程。即,您正在解除对初始请求线程的阻塞,但您正在从线程池中获取另一个线程,该线程可能用于处理另一个请求。唯一的结果是当调用以绝对零增益完成时的上下文切换开销