c# - async-await 的延续爆发——表现不同?

标签 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);
}

问题:

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

enter image description here

但是如果我将延迟更改为:1,2,3

    var task1 = BBB(1);  
    var task2 = BBB(2);  
    var task3 = BBB(3);  

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

enter image description here

最佳答案

请注意,嵌套的消息循环是邪恶的,因为意外的重入实在是太难了(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));
}

Compare the dialog texts (首先是最大堆栈,然后是中等堆栈,然后是最小堆栈)在对话框全部关闭后使用剪贴板(首先是最小堆栈,然后是中等堆栈,然后是最大堆栈)。很明显,对话框是以相反的顺序显示的。

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

  • 第一个延迟触发并调用 MessageBox.Show
  • Win32 MessageBox 函数启动一个嵌套的消息循环并开始设置实际的对话框,其中包含给它自己 的消息(即设置标题、文本等)。请注意,这些调用会发送消息,但它们尚未准备好显示对话框。
  • 第二个延迟触发并跳到那些设置消息前面,它自己调用 MessageBox.Show
  • 第三次延迟也是如此。第三个延迟的消息框实际上完成了设置并显示出来。其他两个消息框仍在(同步地)等待它们的消息循环返回一个值,但是因为那些循环正在运行代码,所以它们无法返回。

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

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

关于c# - async-await 的延续爆发——表现不同?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32573672/

相关文章:

c# - 为什么从网络共享读取文件时会出现 UnauthorizedAccessException?

C# Select Nodes 找不到节点

c# - 等待使用 AutoResetEvent 处理事件

javascript - Jest mockImplementation(Promise.resolve) 在监视从另一个类 (Node.js) 调用的对象时返回未定义

c# - 尝试模拟 Entity Framework 上下文时抛出 TargetInitationException

c# - 并登录 LinkLabel 文本

.net - 如何避免在显示和隐藏控件时更改控件的 z 顺序?

c# - Windows 窗体应用程序中的异步显示

javascript - 使用异步等待时无法获取对象的属性

c# - 为什么 10 亿 * 100 == 12 亿?