简体   繁体   中英

Using a TPL Task to update the UI does not immediately update the UI

I have a .NET Windows Form that creates a task to run asynchronously. That task should call to update the UI of it's progress. It works, but the progress bar only gets updated with some kind of delay.

public partial class WaitDialog : Form
{
    private readonly TaskScheduler _uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();

    private void ReportViewerWaitForm_Load(object sender, EventArgs e)
    {
        _asyncTask = Task.Factory.StartNew(Calculate(), _cancellationTokenSource.Token);   
        _asyncTask.ContinueWith(task => Close(), CancellationToken.None, TaskContinuationOptions.None, _uiScheduler);
    }

    private void Calculate()
    {
        UpdateProgressCount(0, 1000);

        for (int i = 0; i < 1000; i++)
        {
            // do some heavy work here
            UpdateProgressCount(i);
        }
    }

    private void UpdateUserInterface(Action action)
    {
        Task.Factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, _uiScheduler).Wait();
    }

    public void UpdateProgressCount(int count)
    {
        UpdateUserInterface(() => progressBar.Value = count);
    }

    public void UpdateProgressCount(int count, int total)
    {
        UpdateUserInterface(() =>
            {
                progressBar.Minimum = 0;
                progressBar.Maximum = total;
            });
        UpdateProgressCount(count);
    }

    private void WaitForm_FormClosing(object sender, FormClosingEventArgs e)
    {
        if (!_asyncTask.IsCompleted)
        {
            e.Cancel = true;
        }
    }
}

The progress bar is being set correctly, by the time the form closes it's value is set to 1000 (or 100%) but it is not displayed this way on the UI, it only ever shows about 50% completion.

The Task to update the UI is started and then Wait() is called, but the asynchronous task seems to keep running before the UI is updated. I assume this is because the UI thread itself did some sort of BeginInvoke() to update the UI.

Ultimately, by the time the asynchronous (heavy work) task finishes, the UI is not updated completely and the form closes. But the UI task Wait(), Application.DoEvents() or progressBar.Update() have any effect to allow the UI to update before returning to the heavy-work task.

In UpdateProgressCount, you might want to instead invoke your form with the progress. That is the standard way of updating things, not by creating another task.

Additionally, I believe that by waiting on a task that is running on the UI thread, your background threads will continue to run. But I may be incorrect about that part. Anyway, if you invoke your form with the progress that should fix your issue.

Don't create another task for UI updating, calling the invoke method to update the state in WinForm progressBar .

Replace the

UpdateUserInterface(() => progressBar.Value = count)

To

if (progressBar.InvokeRequired) {
    progressBar.Invoke(() => progressBar.Value = count);
} else {
    progressBar.Value = count;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM