Search code examples
asynchronousiprogress

C# Progress method executes after the await returns


I'm trying async await for the first time and running into a problem with using Progress. I call my async method using await and pass in my progress action. What I would like to happen is the progress would be displayed and when the method completes the message to say we're "Done!" is displayed. What happens is that the progress is displayed, "Done!" is displayed and then the last progress message, this is an example of the output: 1, 2,

Done! 3

It appears that the async has returned and the UI context executes before the final Progress has had a chance to run. I can overcome this by adding handlers but with rather follow the pattern correctly.

 public async void button1_Click(object sender, EventArgs e)
    {
        await JustDoItAsync(new Progress<int>(ProgressUpdate));

        textBox1.Text += "\r\nDone!\r\n";
    }

    public async Task JustDoItAsync(IProgress<int> progress)
    {
        for (int i = 0; i < 3; i++)
        {
            await Task.Delay(500);
            progress.Report(i + 1);
        }
    }

    private void ProgressUpdate(int step)
    {
        textBox1.Text += step + "\r\n";
    }

Solution

  • Unfortunately, this behavior is not unexpected. Progress updates are queued, and can arrive after the work has completed. On the other hand, task completion is synchronous (assuming compatible contexts).

    In this case, since you're using IProgress<T>, then your JustDoItAsync should not ever need to access the UI directly. So you should be able to use ConfigureAwait(false) on your awaits in that method, and that should take care of it.

    Alternatively, you could keep a "flag", set it when the task is done, and check it in your progress report, but that makes the code rather awkward:

    public async void button1_Click(object sender, EventArgs e)
    {
      Action<int> progressUpdate = ProgressUpdate;
      await JustDoItAsync(new Progress<int>(update => progressUpdate?.Invoke(update)));
      progressUpdate = null;
      textBox1.Text += "\r\nDone!\r\n";
    }
    

    The other alternative is to embed the flag right in your IProgress<T> implementation.