async-await的延续突发 - 行为不同?

Roy*_*mir 22 c# winforms async-await

我有一个winform代码,点击按钮后运行:

void button1_Click(object sender, EventArgs e)
{
    AAA();
}


async Task BBB(  int delay)
{
    await Task.Delay(TimeSpan.FromSeconds(delay));
    MessageBox.Show("hello");  
}

async Task AAA()
{
    var task1 = BBB(1);  // <--- notice delay=1;  
    var task2 = BBB(1);  // <--- notice delay=1;  
    var task3 = BBB(1);  // <--- notice delay=1;  
    await Task.WhenAll(task1, task2, task3);
}
Run Code Online (Sandbox Code Playgroud)

题 :

为什么我一次看到一个MessageBox delay=1:

在此输入图像描述

但如果我改变延迟:1,2,3-

    var task1 = BBB(1);  
    var task2 = BBB(2);  
    var task3 = BBB(3);  
Run Code Online (Sandbox Code Playgroud)

我看到 - 3个消息框甚至没有点击任何消息框?

在此输入图像描述

Ste*_*ary 16

请注意,嵌套的消息循环是邪恶的,因为意外的重入是刚刚太难(tm).

我认为解释这种行为有两个关键的理解.首先是异步延续 - 就像所有其他"运行此任意代码"Win32消息一样 - 具有比其他消息更高的优先级.第二个是,在运行嵌套消息循环时,有一个长期存在的Win32传统,即发送消息并同步阻止响应.(另一方面,我个人认为,这种可怕的重入 - Win32 API的所有设计都是Windows上绝大多数应用程序错误的原因).

如果以保留堆栈跟踪的方式运行代码,则可以更清楚地看到发生了什么:

void button1_Click(object sender, EventArgs e)
{
    AAA();
}

private List<string> stacks = new List<string>();

async Task BBB(int delay)
{
    await Task.Delay(TimeSpan.FromSeconds(delay));
    var stack = new StackTrace().ToString();
    stacks.Add(stack);
    MessageBox.Show(stack);
}

async Task AAA()
{
    var task1 = BBB(1);  // <--- notice delay=1;  
    var task2 = BBB(1);  // <--- notice delay=1;  
    var task3 = BBB(1);  // <--- notice delay=1;  
    await Task.WhenAll(task1, task2, task3);
    Clipboard.SetText(string.Join("\r\n\r\n", stacks));
}
Run Code Online (Sandbox Code Playgroud)

在对话框全部关闭(首先是最小,然后是中等,然后是最大)之后,将对话框文本(最大堆栈,然后是中等,然后最小)与剪贴板进行比较.很明显,对话框的显示顺序相反.

相信这样的事情正在发生,但没有信心肯定地说:

  • 第一次延迟开始并打电话MessageBox.Show.
  • Win32 MessageBox函数启动一个嵌套的消息循环,并开始设置实际的对话框,其中包含自己的消息(即设置标题,文本等).请注意,这些调用会输出消息,但它们尚未准备好显示对话框.
  • 第二个延迟会自动呼叫,然后在这些设置消息前面跳转并跳转MessageBox.Show.
  • 同样的第三次延迟.第三个延迟的消息框实际上完成了它的设置并显示出来.另外两个消息框仍然(同步)等待它们的消息循环返回一个值,但由于这些循环正在运行代码,它们无法返回.

当您更改时间时1, 2, 3,您仍然会在剪贴板中获得相同的堆栈,但您将看到对话文本现在按顺序排列(最小堆栈首先,然后是中等,然后是最大堆栈).这是因为每个人MessageBox.Show都有足够的时间来设置消息框并建立其消息循环,并在其上面的下一个图层之前显示对话框.

从理论上讲,这种奇怪的行为可以通过一个MessageBox.ShowAsync完全避免嵌套循环的API来避免.不过,我不会屏住呼吸.

  • 有趣的是,使用`Form.ShowDialog`而不是`MessageBox.Show`,没有像这样的重新入侵"种族".它们都会显示在彼此之上,尽管是以"Task.Delay"延续的不可预测的顺序.我想这是因为WinFroms实现了自己的模态消息循环逻辑,而不依赖于Win32`DialogBox` API. (3认同)