Async/await for long-running API methods with progress/cancelation
async-await, backgroundworker, c#
Solution
As noted on the top of that blog post, the information in that post is outdated. You should use the new `IProgress<T>` API provided in .NET 4.5.
If you're using blocking I/O, then make your core method blocking:
public void Build(string sInputFileName, CancellationToken cancel, IProgress<int> progress)
{
using (StreamReader reader = new StreamReader(sInputFileName))
{
int nLine = 0;
int nTotalLines = CountLines(sInputFileName);
while ((sLine = reader.ReadLine()) != null)
{
nLine++;
// do something here...
cancel.ThrowIfCancellationRequested();
if (progress != null) progress.Report(nLine * 100 / nTotalLines);
}
return nLine;
}
}
and then wrap it in `Task.Run` when you call it:
private async void OnDoSomethingClick(object sender, RoutedEventArgs e)
{
OpenFileDialog dlg = new OpenFileDialog { Filter = "Text Files (*.txt)|*.txt" };
if (dlg.ShowDialog() == false) return;
// show the job progress UI...
CancellationTokenSource cts = new CancellationTokenSource();
DummyWorker worker = new DummyWorker();
var progress = new Progress<int>((_, value) => { _progress.Value = value; });
await Task.Run(() => builder.Build(dlg.FileName, cts.Token, progress);
// hide the progress UI...
}
Alternatively, you could rewrite `Build` to use asynchronous APIs and then just call it directly from the event handler without wrapping it in `Task.Run`.
Problem
Edit I suppose the proper way of forcing await to invoke the worker asynchronously is with a Task.Run, like this: ``` await Task.Run(() => builder.Build(dlg.FileName, cts.Token, new Progress(ReportProgress))); ``` Got some light from http://blogs.msdn.com/b/pfxteam/archive/2012/04/12/10293335.aspx. this should be easy but I'm new to async/await so bear with me. I am building a class library exposing an API with some long-running operations. In the past, I used a BackgroundWorker to deal with progress reporting and cancelation, like in this simplified code fragment: ``` public void DoSomething(object sender, DoWorkEventArgs e) { BackgroundWorker bw = (BackgroundWorker)sender; // e.Argument is any object as passed by consumer via RunWorkerAsync... do { // ... do something ... // abort if requested if (bw.CancellationPending) { e.Cancel = true; break; } //eif // notify progress bw.ReportProgress(nPercent); } } ``` and the client code was like: ``` BackgroundWorker worker = new BackgroundWorker { WorkerReportsProgress = true, WorkerSupportsCancellation = true }; worker.DoWork += new DoWorkEventHandler(_myWorkerClass.DoSomething); worker.ProgressChanged += WorkerProgressChanged; worker.RunWorkerCompleted += WorkerCompleted; worker.RunWorkerAsync(someparam); ``` Now I'd like to leverage the new async pattern. So, first of all here is how I'd write a simple long-running method in my API; here I'm just reading a file line by line, just to emulate a real-world process where I'll have to convert a file format with some processing: ``` public async Task DoSomething(string sInputFileName, CancellationToken? cancel, IProgress progress) { using (StreamReader reader = new StreamReader(sInputFileName)) { int nLine = 0; int nTotalLines = CountLines(sInputFileName); while ((sLine = reader.ReadLine()) != null) { nLine++; // do something here... if ((cancel.HasValue) && (cancel.Value.IsCancellationRequested)) break; if (progress != null) progress.Report(nLine * 100 / nTotalLines); } return nLine; } } ``` For the sake of this sample, say this is a method of a DummyWorker class. Now, here is my client code (a WPF test app): ``` private void ReportProgress(int n) { Dispatcher.BeginInvoke((Action)(() => { _progress.Value = n; })); } private async void OnDoSomethingClick(object sender, RoutedEventArgs e) { OpenFileDialog dlg = new OpenFileDialog { Filter = "Text Files (*.txt)|*.txt" }; if (dlg.ShowDialog() == false) return; // show the job progress UI... CancellationTokenSource cts = new CancellationTokenSource(); DummyWorker worker = new DummyWorker(); await builder.Build(dlg.FileName, cts.Token, new Progress(ReportProgress)); // hide the progress UI... } ``` The implementation for the IProgress interface comes from http://blog.stephencleary.com/2010/06/reporting-progress-from-tasks.html, so you can refer to that URL. Anyway, in this usage test the UI is effectively blocked and I see no progress. So what would be the full picture for such a scenario, with reference to the consuming code?