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:
- By convention, the
progress
parameter may benull
if the caller doesn't need progress reports, so be sure to check for this in yourasync
method. - 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
. - The
Progress<T>
type will capture the current context (e.g., UI context) on construction and will raise itsProgressChanged
event in that context. So you don't have to worry about marshaling back to the UI thread before callingReport
.
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 ™
.