I\'m completely new to C# 5\'s new async
/await
keywords and I\'m interested in the best way to implement a progress event.
Now I\'d prefer
When using a Task.Run lambda I have used an Invoke Action inside of this to update a ProgressBar control. This may not be the best way but it worked in a pinch without having to restructure anything.
Invoke(new Action(() =>
{
LogProgress();
}));
Which takes it to...
private void LogProgress()
{
progressBar1.Value = Convert.ToInt32((100 * (1.0 * LinesRead / TotalLinesToRead)));
}
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 ™
.
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:
progress
parameter may be null
if the caller doesn't need progress reports, so be sure to check for this in your async
method.Progress
.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
.