【问题标题】:Async / Await by example异步/等待示例
【发布时间】:2019-04-12 02:07:42
【问题描述】:

好的,我想我几乎可以理解async / await 和多线程上的所有模糊问题了。我知道异步是关于任务的,多线程是关于工人的。所以你可以在同一个线程上运行不同的任务(this answer 很好地解释了它)。

所以我做了一个小程序来看看神奇的发生,我有点困惑:

public class Program
{
    public static async Task Main(string[] args)
    {
        var task = ToastAsync();
        Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] Cleaning the kitchen...");
        await task;
        Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] Take the toast");
    }

    public async static Task ToastAsync()
    {
        Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] Putting the toast");
        Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] Setting a timer");
        await Task.Delay(2000);
        Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] Toast is ready");
    }
}

在第一次运行这个程序之前,我希望它可以在单个线程上运行。就像我上面链接的答案一样,我希望这相当于“在吐司计时器运行时打扫厨房”。结果与它相矛盾:

[1] Putting the toast
[1] Setting a timer
[1] Cleaning the kitchen...
[4] Toast is ready
[4] Take the toast

上面的结果对我来说没有多大意义。到底发生了什么?似乎函数的一部分正在一个线程中执行,然后,当它到达await 点时,它会将执行处理到另一个线程......?我什至不知道这是可能的 D:

此外,我稍微改变了上面的例子。在主函数中,我使用了task.Wait(),而不是await task;。然后结果变了:

[1] Putting the toast
[1] Setting a timer
[1] Cleaning the kitchen...
[4] Toast is ready
[1] Take the toast

现在这看起来更像示例。就像烤面包上的计时器一样,不过是一个不同的“炊具”。但是为什么它与使用await 不同呢?有没有办法在一个线程中完全获取异步任务?我的意思是,在thread 1 上也有Toast is ready

感谢异步!

【问题讨论】:

  • 你真的需要它在一个线程上运行吗?任务是对线程的一种抽象。
  • 另外,尽可能避免.Wait(),它会阻塞当前线程直到任务完成,这违背了async的目的。

标签: c# multithreading asynchronous .net-core async-await


【解决方案1】:

需要注意的是

  1. 当您调用ToastAsync 时,它启动 任务 Hot(即使您没有调用await)。注意:开始一个任务并不意味着开始一个线程......这反过来解释了为什么“打扫厨房”。在“敬酒”的背后)

  2. async 方法将一直运行,直到遇到第一个 await 关键字,并将控制权交还给调用者。

  3. 因为 控制台应用程序中没有 SynchronizationContext。 Compiler 认为没有必要在调用 thread 上创建 Continuation,这反过来解释了为什么“Take the toast”与“Toast is ready”在同一个线程上.

注意:如果你把厨房弄得乱七八糟后打扫干净,你不必事先打扫干净

来自 cmets

我明白,await 总是会从线程中获取一个线程 池来运行该方法(这真的让我大吃一惊)因此 基本上不可能有 async / await 单线程

Async and Await 模式与多线程本身无关,它与可扩展性和 IO 绑定工作负载和/或 UI 响应能力(在 UI 框架)。

看看 Stephen Cleary 关于这个主题的一些文章:

Async and Await - Stephen Cleary

【讨论】:

  • 嗨迈克尔,谢谢!两件事:首先,在您的第一个陈述中,您的意思是相反的,对吗? “敬酒是在打扫厨房”。其次,我明白await 总是会从线程池中获取一个线程来运行该方法(这真的让我大吃一惊)因此基本上不可能有一个async / await 单线程......?跨度>
  • @JoãoMenighin 当你调用 var task = ToastAsync() 时,它会启动任务(不是线程),它将在 context 中运行ToastAsync 的第一部分它的调用形式直到它到达第一个 await 这就是为什么在“Put the toast”之后会记录“Cleaning the kitchen...”
  • 啊,好吧,对不起,这是一个英语问题……把“后面”误认为“之前”。是的,这很清楚。 “异步等待单线程”呢?
  • @JoãoMenighin 忘记 Async and Await 模式中的 Wait()Result(),如果你发现自己想要使用它们,你可能做错了什么跨度>
  • @MichaelRandall 很好的答案,像往常一样!但是,我认为这部分:“Asnyc 和 Await 模式本身与多线程无关,它与可伸缩性和 IO 绑定工作负载有关” 也值得成为答案的一部分。当您不再将async/await 视为multithreading - 您开始看到真相。
【解决方案2】:

如果您从 Windows 窗体的事件中运行相同的代码,您将获得预期的结果:

[1] Putting the toast
[1] Setting a timer
[1] Cleaning the kitchen...
[1] Toast is ready
[1] Take the toast

这是因为await 默认情况下会在等待后恢复上下文,但这是有代价的,因为这是额外的事情。如果您不需要上下文恢复,您可以像这样配置等待:

await Task.Delay(2000).ConfigureAwait(false);

...您将看到与控制台应用程序相同的输出。

请记住,等待不是并发执行操作,而是保持 UI 响应。您希望能够在先前订单的吐司正在烹饪的同时继续接收新的吐司订单。

【讨论】:

  • 也谢谢西奥多!这也让我清醒了!如果可以的话,我会给你们两个答案!
  • @João Menighin 没关系。 Michael Randall 的回答比我的更中肯,我也从中学到了一些东西。 :-)
猜你喜欢
  • 2018-01-05
  • 2020-11-10
  • 2017-12-02
  • 2021-09-18
  • 1970-01-01
  • 2023-03-12
相关资源
最近更新 更多