C# async/await Progress event on Task<> object

The recommended approach is described in the Task-based Asynchronous Pattern documentation, which gives each asynchronous method its own IProgress<T>:

public async Task PerformScanAsync(IProgress<MyScanProgress> progress)
{
  ...
  if (progress != null)
    progress.Report(new MyScanProgress(...));
}

Usage:

var progress = new Progress<MyScanProgress>();
progress.ProgressChanged += ...
PerformScanAsync(progress);

Notes:

  1. By convention, the progress parameter may be null if the caller doesn't need progress reports, so be sure to check for this in your async method.
  2. Progress reporting is itself asynchronous, so you should create a new instance of your arguments each time you call (even better, just use immutable types for your event args). You should not mutate and then re-use the same arguments object for multiple calls to Progress.
  3. The Progress<T> type will capture the current context (e.g., UI context) on construction and will raise its ProgressChanged event in that context. So you don't have to worry about marshaling back to the UI thread before calling Report.

Simply put, Task doesn't support progress. However, there's already a conventional way of doing this, using the IProgress<T> interface. The Task-based Asynchronous Pattern basically suggests overloading your async methods (where it makes sense) to allow clients to pass in an IProgess<T> implementation. Your async method would then report progress via that.

The Windows Runtime (WinRT) API does have progress indicators built-in, in the IAsyncOperationWithProgress<TResult, TProgress> and IAsyncActionWithProgress<TProgress> types... so if you're actually writing for WinRT, those are worth looking into - but read the comments below as well.


I had to piece together this answer from several posts as I was trying to figure out how to make this work for code that is less trivial (ie events notify changes).

Let's assume you have a synchronous item processor that will announce the item number it is about to start work on. For my example I am just going to manipulate the content of the Process button, but you can easily update a progress bar etc.

private async void BtnProcess_Click(object sender, RoutedEventArgs e)
{       
    BtnProcess.IsEnabled = false; //prevent successive clicks
    var p = new Progress<int>();
    p.ProgressChanged += (senderOfProgressChanged, nextItem) => 
                    { BtnProcess.Content = "Processing page " + nextItem; };

    var result = await Task.Run(() =>
    {
        var processor = new SynchronousProcessor();

        processor.ItemProcessed += (senderOfItemProcessed , e1) => 
                                ((IProgress<int>) p).Report(e1.NextItem);

        var done = processor.WorkItWorkItRealGood();

        return done ;
    });

    BtnProcess.IsEnabled = true;
    BtnProcess.Content = "Process";
}

The key part to this is closing over the Progress<> variable inside ItemProcessed subscription. This allows everything to Just works ™.

Tags:

C#

Async Await