Async ShowDialog

2020-02-23 06:50发布

I'm using async/await to asynchronously load my data from database and during the loading process, I want to popup a loading form, it's just a simple form with running progress bar to indicate that there's a running process. After data has been loaded, the dialog will automatically be closed. How can I achieve that ? Below is my current code:

 protected async void LoadData() 
    {
       ProgressForm _progress = new ProgressForm();  
       _progress.ShowDialog()  // not working
       var data = await GetData();          
       _progress.Close();
    }

Updated:

I managed to get it working by changing the code:

 protected async void LoadData() 
        {
           ProgressForm _progress = new ProgressForm();  
           _progress.BeginInvoke(new System.Action(()=>_progress.ShowDialog()));
           var data = await GetData();          
           _progress.Close();
        }

Is this the correct way or there's any better ways ?

Thanks for your help.

4条回答
家丑人穷心不美
2楼-- · 2020-02-23 07:19

ShowDialog() is a blocking call; execution will not advance to the await statement until the dialog box is closed by the user. Use Show() instead. Unfortunately, your dialog box will not be modal, but it will correctly track the progress of the asynchronous operation.

查看更多
劫难
3楼-- · 2020-02-23 07:23

It's easy to implement with Task.Yield, like this (WinForms, no exception handling for simplicity):

namespace WinFormsApp
{
    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... " };

            var progressFormTask = progressForm.ShowDialogAsync();

            var data = await LoadDataAsync();

            progressForm.Close();
            await progressFormTask;

            MessageBox.Show(data.ToString());
        }
    }

    internal static class DialogExt
    {
        public static async Task<DialogResult> ShowDialogAsync(this Form @this)
        {
            await Task.Yield();
            if (@this.IsDisposed)
                return DialogResult.OK;
            return @this.ShowDialog();
        }
    }
}

It's important to understand here how the execution flow jumps over to a new nested message loop (that of the modal dialog) and then goes back to the original message loop (that's what await progressFormTask is for).

查看更多
手持菜刀,她持情操
4楼-- · 2020-02-23 07:28

You could try the following:

protected async void LoadData()
{
    ProgressForm _progress = new ProgressForm();
    var loadDataTask = GetData();
    loadDataTask.ContinueWith(a =>
        this.Invoke((MethodInvoker)delegate
        {
            _progress.Close();
        }));
    _progress.ShowDialog();
}
查看更多
We Are One
5楼-- · 2020-02-23 07:33

Here's a form that uses Task.ContinueWith and should avoid any race condition with your use of the modal ProgressForm:

protected async void LoadDataAsync()
{
    ProgressForm _progress = 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(() =>
        {   
            _progress.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()
        _progress.Close();
    }), TaskScheduler.FromCurrentSynchronizationContext());
}
查看更多
登录 后发表回答