async-await's continuations bursts — behave differently?

心不动则不痛 提交于 2019-12-31 13:13:37

问题


I have a winform code which run after a button click :

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

Question :

Why do I see one MessageBox at a time when delay=1 :

But If I change delay to : 1,2,3

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

I see - 3 Message Boxes without even clicking any Messagebox?

  • Thanks to @Noseratio for pointing that behaviour at first place.

回答1:


Please note that nested message loops are evil because unexpected reentrancy is Just Too Darn Hard(tm).

I think there are two key pieces of understanding to explain this behavior. The first is that async continuations - like all other "run this arbitrary code" Win32 messages - have a higher priority than other messages. The second is that there's a long-standing Win32 tradition of sending messages and synchronously blocking for a response while running a nested message loop. (On a side note, it is my personal opinion that this horrible reentrancy-everywhere design of the Win32 API has been responsible for the vast majority of application bugs on Windows).

If you run your code in a way that preserves stack traces, you can see more clearly what's going on:

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 (largest stack first, then medium, then smallest) with the clipboard after the dialogs are all closed (smallest first, then medium, then largest). It's clear that the dialogs are being displayed in the reverse order.

I believe something like this is happening, but lack the confidence to say for sure:

  • The first delay fires off and calls MessageBox.Show.
  • The Win32 MessageBox function starts a nested message loop and starts setting up the actual dialog with messages to itself (i.e., setting caption, text, etc). Note that these calls pump messages but they're not ready to show the dialog yet.
  • The second delay fires off and jumps in front of those setup messages with its own call to MessageBox.Show.
  • Similarly for the third delay. The third delay's message box actually completes its setup and gets shown. The other two message boxes are still (synchronously) waiting for their message loops to return a value, but because those loops are running code, they can't return.

When you change the timings to 1, 2, 3, you'll still get the same stacks in the clipboard, but you'll see the dialog texts are now in order (smallest stack first, then medium, then largest). This is because each MessageBox.Show has sufficient time to set up the message box and establish its message loop and show the dialog before the next one layers on top of it.

In theory, this odd behavior could be avoided by a MessageBox.ShowAsync API that avoids the nested loops completely. I wouldn't hold my breath for it, though.



来源:https://stackoverflow.com/questions/32573672/async-awaits-continuations-bursts-behave-differently

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!