任务如何并发运行?

How tasks can run concurrently?

提问人:Mohab Alnajjar 提问时间:2/28/2023 最后编辑:Mohab Alnajjar 更新时间:2/28/2023 访问量:224

问:

我清楚地了解 C# 中的 TAP 模型执行。 但是,当涉及到并发任务时,我会感到困惑。

请考虑 MS 文档中的以下示例:

Coffee cup = PourCoffee();
Console.WriteLine("Coffee is ready");

Task<Egg> eggsTask = FryEggsAsync(2);
Task<Bacon> baconTask = FryBaconAsync(3);
Task<Toast> toastTask = ToastBreadAsync(2);

Toast toast = await toastTask;
ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("Toast is ready");
Juice oj = PourOJ();
Console.WriteLine("Oj is ready");

Egg eggs = await eggsTask;
Console.WriteLine("Eggs are ready");
Bacon bacon = await baconTask;
Console.WriteLine("Bacon is ready");

Console.WriteLine("Breakfast is ready!");

打电话而不打电话,这非常令人困惑。asyncawait

让我们以这一行为例:Task<Egg> eggsTask = FryEggsAsync(2);

1- 主线程将调用该函数并开始执行它。

2-当主线程计数第一个线程时,它将返回函数。awaitMain()

现在,当完成时发生了什么?await Task.Delay(3000);FryEggsAsync()

private static async Task<Egg> FryEggsAsync(int howMany)
{
    Console.WriteLine("Warming the egg pan...");
    await Task.Delay(3000);
    Console.WriteLine($"cracking {howMany} eggs");
    Console.WriteLine("cooking the eggs ...");
    await Task.Delay(3000);
    Console.WriteLine("Put eggs on plate");

    return new Egg();
}

我尝试使用 Rider 调试此示例,当完成从线程池中生成线程并继续执行时,我得到了它。await Task.Delay(3000);FryEggsAsync()

这是否意味着这三个函数中的每一个都将使用池中的线程在后台继续执行,而无需在主线程上继续执行?

或者函数在行处挂起,直到我从函数中取出才继续?awaitawaitMain()

C# 异步 async-await 并发 任务并行库

评论


答:

1赞 Guru Stron 2/28/2023 #1

现在,当 await Task.Delay(3000);在 FryEggsAsync() 中完成发生了什么?

调用延续(即 之后的所有内容)。在控制台应用程序的“简单”/默认情况下,它将在线程池上调用,但存在时有一些警告。await Task.Delay(3000)SynchronizationContext

这是否意味着这三个函数中的每一个都将使用池中的线程在后台继续执行,而无需在主线程上继续执行?

对于示例控制台应用 - 是的。

或者该函数在 await 行暂停,直到我从 Main() 函数等待它才继续?

不。 在需要等待执行任务完成的情况下,需要返回执行 Main 的线程控件。awaitMainMain

阅读更多:

  1. .NET 中的异步编程 - 简介、误解和问题 - 关于该主题的精彩文章
  2. 异步大师 Stephen Cleary 的 There Is No Thread(也阅读他的其他文章)
  3. SynchronizationContext 有什么作用?
  4. 使用 SynchronizationContext 时 async/await 死锁
  5. 剖析 C# 中的异步方法 - 深入了解编译器生成的状态机的描述async
1赞 JonasH 2/28/2023 #2

Task它本身并没有说明并发性。它只是表示一个操作,可能有一个结果。它可能已经完成,将来可能完成,也可能永远不会完成。它并没有真正说明线程。在许多情况下,它将表示一个异步 IO 操作,该操作从不使用任何线程来运行。

await本质上是说,当任务完成时,继续执行。并在与开始时相同的“上下文”中执行此操作。即,如果从 UI 线程调用,则为 UI 线程,如果从线程池调用,则为某个线程池线程。请注意,控制台程序没有任何 UI 线程,因此唯一的上下文是线程池上下文。

Task.Delay(3000);本质上只是一个包装器。因此,在延迟后,操作系统将向线程池发送消息,请在某个可用线程上运行此代码。线程池将这样做,该代码将完成任务。接下来会发生什么将取决于每个等待者捕获的上下文。Threading.Timer

这是否意味着这三个函数中的每一个都将使用池中的线程在后台继续执行,而无需在主线程上继续执行?

由于该示例使用控制台程序,因此没有 UI 线程。有一个主线程,但它仍然使用线程池上下文。因此,await 之后的任何内容都可能在线程池线程上调用。这是有效的,因为它使用 ,即一旦主任务完成,程序将终止。Task Main

评论

0赞 Mohab Alnajjar 3/1/2023
哦。我明白了。但是,假设我有一个 GUI 应用程序,根据我的理解,也许是 WinForms;我们有 GUI 主线程,因此在这种情况下会有 SynchronizationContext,并且需要在调用它的线程上执行 continuation。如果有任何错误,请纠正我
0赞 JonasH 3/1/2023
@MohabAlnajjar 是的,如果在 UI 线程上调用异步方法,则延续也将在 UI 线程上运行。最简单的情况是,如果你有一个 UI 程序,并且只等待返回任务的第三方函数,那么你的所有代码都应该在 UI 线程上运行,不必担心线程问题。