robertkroll
robertkroll

Reputation: 8794

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.

Upvotes: 3

Views: 1445

Answers (2)

Turbot
Turbot

Reputation: 5233

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

Upvotes: 0

user981225
user981225

Reputation: 9252

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.

Upvotes: 1

Related Questions