新的C# 5.0中的'async'和'await'关键字是否使用多个核心?

65

在C# 5.0语言中新增了两个关键字,分别是asyncawait。这两个关键字协同工作,能够使得C#方法异步运行而不会阻塞调用线程。

我的问题是,这些方法是否能够充分利用多核心并行运行,或者async方法在与调用者相同的线程核心上运行?


6
这取决于异步方法返回的可等待对象。 - phoog
1
它们都只是语法糖。线程取决于当前的“同步上下文”和您等待的“任务”。 - CodesInChaos
4个回答

101
C# 5.0语言新增了async和await两个关键字,它们搭配使用可以在不阻塞调用线程的情况下异步运行C#方法。然而,需要明确一点:await并不能奇迹般地使同步方法变成异步方法。例如,它不能在新线程上启动该方法。被调用的方法必须知道如何以异步方式运行自己,并由它自己决定采取何种方式。至于这些方法是否利用多核心并行运行或者在与调用线程相同的线程核心中运行,完全取决于被调用的方法。await指示编译器将该方法重写为委托,作为异步任务的继续传递。也就是说,await FooAsync()表示“调用FooAsync(),并且返回的内容必须是代表刚刚启动的异步操作的内容。告诉该对象,当它知道异步操作已完成时,应调用此委托”。该委托具有一个属性,即在调用时,当前方法似乎从上次离开的地方恢复执行。如果被调用的方法将工作调度到另一个亲和另一个核心的线程上,那太好了。如果它在 UI 线程上启动计时器并在将来触发某个事件处理程序,那也很好。await并不在意这些,它只是确保异步作业完成后,控制可以从离开的地方恢复执行。当异步任务结束并且控制权从离开的位置继续时,是否在与之前相同的线程上执行,这是你没有问但可能应该问的问题。

这取决于上下文。在WinForms应用程序中,如果你等待UI线程的一些操作,控制权会再次回到UI线程。但在控制台应用程序中,则可能不会。


2
我喜欢你在这里的解释,它真的帮助我更好地理解异步和等待。 - Icemanind
1
如果您在UI线程上等待某些东西,控件通常会再次在UI线程上运行,但不一定如此。这也取决于“代表异步操作的某些东西”。例如,当您等待someTask.ConfigureAwait(false)时,就会发生这种情况。 - svick

71

Eric Lippert的回答非常优秀,我只是想进一步描述async并发性。

简单的“串行”方法是在每次只有一个操作时使用 await

static void Process()
{
  Thread.Sleep(100); // Do CPU work.
}

static async Task Test()
{
  await Task.Run(Process);
  await Task.Run(Process);
}
在这个例子中,Test方法会将Process排入线程池,当它完成后,会再次将Process排入线程池。 Test方法在大约200毫秒后完成。在任何时候,只有一个线程真正推进进度。
并行化这个简单的方法是使用Task.WhenAll:
static void Process()
{
  Thread.Sleep(100); // Do CPU work.
}

static async Task Test()
{
  // Start two background operations.
  Task task1 = Task.Run(Process);
  Task task2 = Task.Run(Process);

  // Wait for them both to complete.
  await Task.WhenAll(task1, task2);
}
在这个例子中,Test方法将Process两次排队到线程池中,并等待它们都完成。在大约100毫秒后,Test方法将完成。 Task.WhenAll(和Task.WhenAny)是通过async/await引入以支持简单的并行处理。然而,如果您需要更高级的内容(真正的CPU绑定并行处理更适合TPL),则TPL仍然可用。TPL与async/await很好地协作。
我在我的介绍async博客文章中涵盖了基本的async并行处理,以及Eric所提到的“上下文”。

Stephen,你提到“TPL与async/await很搭配”。你能给我指一些关于这个特定主题的好资料吗? - Patrick
@Patrick:如果你需要同时进行并行处理和异步操作,请查看TPL Dataflow。 - Stephen Cleary

4
一个异步方法返回一个可等待对象(具有GetAwaiter方法),如果您使用await关键字调用该方法,编译器可以生成消耗该对象的代码。您也可以自由地调用这样的方法without使用await关键字,并显式地消耗该对象。
该对象封装了一个异步操作,该操作可能在另一个线程上运行,也可能不在。Eric Lippert的文章Asynchrony in C# 5.0 part Four: It's not magic考虑了涉及仅一个线程的异步编程示例。

2

asyncawait是基于TPL的,因此它们应该非常相似。默认情况下,您应该将它们视为在单独的线程上运行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接