Async ShowDialog
Solution 1:
It's easy to implement with Task.Yield
, like below (WinForms, no exception handling for simplicity). It's important to understand how the execution flow jumps over to a new nested message loop here (that of the modal dialog) and then goes back to the original message loop (that's what await progressFormTask
is for):
namespace WinFormsApp
{
internal static class DialogExt
{
public static async Task<DialogResult> ShowDialogAsync(this Form @this)
{
await Task.Yield();
if (@this.IsDisposed)
return DialogResult.Cancel;
return @this.ShowDialog();
}
}
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
async Task<int> LoadDataAsync()
{
await Task.Delay(2000);
return 42;
}
private async void button1_Click(object sender, EventArgs e)
{
var progressForm = new Form() {
Width = 300, Height = 100, Text = "Please wait... " };
object data;
var progressFormTask = progressForm.ShowDialogAsync();
try
{
data = await LoadDataAsync();
}
finally
{
progressForm.Close();
await progressFormTask;
}
// we got the data and the progress dialog is closed here
MessageBox.Show(data.ToString());
}
}
}
Solution 2:
Here's a pattern that uses Task.ContinueWith and should avoid any race condition with your use of the modal ProgressForm:
protected async void LoadDataAsync()
{
var progressForm = new ProgressForm();
// 'await' long-running method by wrapping inside Task.Run
await Task.Run(new Action(() =>
{
// Display dialog modally
// Use BeginInvoke here to avoid blocking
// and illegal cross threading exception
this.BeginInvoke(new Action(() =>
{
progressForm.ShowDialog();
}));
// Begin long-running method here
LoadData();
})).ContinueWith(new Action<Task>(task =>
{
// Close modal dialog
// No need to use BeginInvoke here
// because ContinueWith was called with TaskScheduler.FromCurrentSynchronizationContext()
progressForm.Close();
}), TaskScheduler.FromCurrentSynchronizationContext());
}