As far as I can see when an "async void" method (such as an event handler) is called, the caller can never know when it has completed (as it can't wait for the Task
to complete). So effectively its a fire and forget call.
This is demonstrated with this code (i've put a Button and TabControl onto a form and hooked up the 2 events). When the button is clicked it changes the tab, this causes the SelectedIndexChanged
event to be raised which is async.
private void button1_Click(object sender, EventArgs e)
{
Debug.WriteLine("Started button1_Click");
tabControl1.SelectedTab = tabControl1.SelectedIndex == 0 ? tabPage2 : tabPage1;
Debug.WriteLine("Ended button1_Click");
}
private async void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
{
Debug.WriteLine("Started tabControl1_SelectedIndexChanged");
await Task.Delay(1000);
Debug.WriteLine("Ended tabControl1_SelectedIndexChanged");
}
The resulting output is
Started button1_Click
Started tabControl1_SelectedIndexChanged
Ended button1_Click
Ended tabControl1_SelectedIndexChanged
As you can see the SelectedIndexChanged
event handler was fired, but the caller did not wait not wait for it to complete (it couldn't as it had no Task to await).
My Proposed solution
Instead of using async
the event handler waits on any Async
methods it uses, then everything seems to work... It waits by polling the Task.IsCompleted
property while calling DoEvents
in order to keep the async task alive and processing (in this case Task.Delay).
private void button1_Click(object sender, EventArgs e)
{
Debug.WriteLine("Started button1_Click");
tabControl1.SelectedTab = tabControl1.SelectedIndex == 0 ? tabPage2 : tabPage1;
Debug.WriteLine("Ended button1_Click");
}
private void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
{
Debug.WriteLine("Started tabControl1_SelectedIndexChanged");
Await(Task.Delay(1000));
Debug.WriteLine("Ended tabControl1_SelectedIndexChanged");
}
public static void Await(Task task)
{
while (task.IsCompleted == false)
{
System.Windows.Forms.Application.DoEvents();
}
if (task.IsFaulted && task.Exception != null)
throw task.Exception;
else
return;
}
This now gives the expected result
Started button1_Click
Started tabControl1_SelectedIndexChanged
Ended tabControl1_SelectedIndexChanged
Ended button1_Click
Can anyone see any issues with taking this approach???