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