【问题标题】:Random sequence of execution of statements when using async await使用异步等待时语句的随机执行顺序
【发布时间】:2020-09-11 02:06:39
【问题描述】:

我有一个非常简单的程序,如下所示。

 public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            Task task = DemoCompletedAsync();

            MessageBox.Show("Method returned");            
            MessageBox.Show("Exiting .....");
        }

        static async Task<string> DemoCompletedAsync()
        {
            MessageBox.Show("Before first await");

            await Task.Run(() => GetID());

            MessageBox.Show("After first await");

            return "Demo";
        }

        public static int GetID()
        {
            Thread.Sleep(2000);
            return 1;
        }
    }

在没有调试模式的情况下运行程序时,消息框会以正确的顺序出现。 即
“在第一次等待之前”
“方法返回”
“退出......”
“第一次等待之后”

但在调试时,语句会以随机方式执行,不知何故在 MainWindow()DemoCompletedAsync 之间来回切换
框出的消息以下列方式显示:
“在第一次等待之前”——来自DemoCompletedAsync
“方法返回”---来自MainWWindow
“第一次等待之后”——来自DemoCompletedAsync
“退出……”——来自MainWindow

根据理论,在方法名称中使用 async 关键字告诉编译器该方法将等待使用 await 关键字的异步操作,并且一旦遇到 await 关键字,控制就会传递调用代码。但是,由 await 关键字表示的异步操作将继续执行。一旦异步操作结束,方法的其余部分将被执行。那么为什么我会出现这种奇怪的行为,例如一些语句从MainWindow 执行,一些语句从DemoCompletedAsync 相互切换。我还观察到另一个与消息框相关的问题。 MessageBox.Show 是模态对话框。第二个不应该出现,直到第一个关闭。但在给定程序的情况下,当控制在MainWindowDemoCompletedAsync 之间切换时,我实际上同时得到了两个消息框。

【问题讨论】:

  • 消息框“任务已完成”显示在刚刚创建任务但尚未等待的位置。所以这个信息是骗人的。我建议在消息中包含task.Status
  • 你实际上并没有在等待任务,因为你的主要方法没有等待它,所以消息会立即显示,你到底想做什么?这只是一个任务实验?
  • @Charleh 是的,我正在试验任务。是的,消息将立即显示。但它们应该按顺序显示,即来自MainWindow 的消息或来自DemoCompletedAysnc 的消息。为什么有些消息显示来自MainWindow,然后来自DemoCompletedAsync,又来自MainWindow。来自MainWindow 的消息应按顺序显示。

标签: c# asynchronous async-await


【解决方案1】:

所有这些都是因为这条线:

Task task = DemoCompletedAsync();

你没有等待这个任务,所以当指令指针到达真正的线程(这里是Task.Run(() =&gt; GetID());)时,下一行立即执行。因此,执行的下一行可能是 MessageBox.Show("Method returned");MessageBox.Show("After first await"); randomely 之一!

但这里的重点是您不能等待这个任务,因为您在构造函数中并且它不能是异步的。你也不能像DemoCompletedAsync().Wait() 那样等待这个任务,因为你在Synchronization Context 并且会发生死锁。

所以你应该这样称呼它:

DemoCompletedAsync().ConfigureAwait(false).GetAwaiter().GetResult();

并在DemoCompletedAsync 方法中使用Dispatcher.Invoke 进行GUI 操作

【讨论】:

  • GetResult 应该避免。很少需要 ConfigureAwait。
  • 当你不想返回 UI 线程时,你应该使用ConfigureAwait(false)。例如,它应该用于存储库或与 UI 无关的东西。
  • @HenkHolterman:你是对的 Henk。我们应该尽可能避免阻塞线程,但我试图解释 senarios 并根据其当前结构回答问题。此外,始终建议使用ConfigureAwait(false) 来防止死锁。 +1 对于您提到重组为 Form_Load 事件的答案
  • @Michael:请参考我上面的评论。另请参阅我的答案中的最后一行,这是您提到的有关 UI 的解决方案
  • @Arman,我的评论是对@HenkHolterman 的评论。但是ConfigureAwait(false) 并不是为了防止死锁。我同意它可以做到这一点,但它不应该到处都是。您需要知道它的作用,因此您需要了解ContextConfigureAwait 仅适用于您实际上要执行 await 的情况。 GetAwaiter` 不算数。如果您在 UI 线程上并且有 await 语句,您通常会在 await 之后返回到 UI 线程。如果您等待的任务有ConfigureAwait(false),它将返回另一个上下文,可能是一个新线程。
猜你喜欢
  • 1970-01-01
  • 2016-06-08
  • 2023-01-20
  • 1970-01-01
  • 2015-10-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多