Async / waiting for many years of API methods with progress / cancellation

Edit I believe that the correct way to force the worker to wait for an asynchronous call using Task.Run, for example:

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 .


It should be easy, but I'm new to async / await, so bear with me. I am creating a class library by exposing an API with some lengthy operations. I used to use BackgroundWorker to process and report reports of results, for example, in this simplified code snippet:

 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 client code:

 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 would like to use the new async template. So, first of all, this is how I will write a simple simple method in my API; here I just read the file line by line, just to emulate the real process, where I will have to convert the 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 this example, let's say that this is a method of the DummyWorker class. Now here is my client code (WPF test application):

 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 of the IProgress interface comes from http://blog.stephencleary.com/2010/06/reporting-progress-from-tasks.html , so you can link to this URL. Anyway, in this test of use the user interface is effectively blocked, and I see no progress. So, what will be the full picture for such a scenario with reference to the consumption code?

+6
source share
1 answer

As noted at the top of this blog post, the information in this post is outdated. You should use the new IProgress<T> API provided in .NET 4.5.

If you use blocking I / O, then block the main method:

 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 can rewrite Build to use asynchronous APIs, and then simply call it directly from the event handler without wrapping it in Task.Run .

+9
source

All Articles