异步/等待具有进度/取消的长时间运行的API方法

编辑我认为强制等待异步调用worker的正确方法是使用Task.Run,​​如下所示:

await Task.Run(() => builder.Build(dlg.FileName, cts.Token, new Progress(ReportProgress))); 

从http://blogs.msdn.com/b/pfxteam/archive/2012/04/12/10293335.aspx获得了一些亮点。


这应该很容易,但我是async / await的新手,所以请耐心等待。 我正在构建一个类库,使用一些长时间运行的操作来公开API。 在过去,我使用BackgroundWorker来处理进度报告和取消,就像在这个简化的代码片段中一样:

 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); } } 

和客户端代码是这样的:

 BackgroundWorker worker = new BackgroundWorker { WorkerReportsProgress = true, WorkerSupportsCancellation = true }; worker.DoWork += new DoWorkEventHandler(_myWorkerClass.DoSomething); worker.ProgressChanged += WorkerProgressChanged; worker.RunWorkerCompleted += WorkerCompleted; worker.RunWorkerAsync(someparam); 

现在我想利用新的异步模式。 所以,首先我要在API中编写一个简单的长期运行方法; 在这里我只是逐行读取一个文件,只是为了模拟一个真实世界的过程,我将不得不通过一些处理来转换文件格式:

 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; } } 

为了这个示例,请说这是DummyWorker类的方法。 现在,这是我的客户端代码(WPF测试应用程序):

 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... } 

IProgress界面的实现来自http://blog.stephencleary.com/2010/06/reporting-progress-from-tasks.html ,因此您可以引用该URL。 无论如何,在这个使用测试中,UI被有效阻止,我看不到任何进展。 那么,参考消费代码,这样一个场景的全貌是什么?

正如该博客文章顶部所述,该post中的信息已过时。 您应该使用.NET 4.5中提供的新IProgress API。

如果您正在使用阻止I / O,那么请阻止您的核心方法:

 public void Build(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... cancel.ThrowIfCancellationRequested(); if (progress != null) progress.Report(nLine * 100 / nTotalLines); } return nLine; } } 

然后在调用它时将其包装在Task.Run

 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((_, value) => { _progress.Value = value; }); await Task.Run(() => builder.Build(dlg.FileName, cts.Token, progress); // hide the progress UI... } 

或者,您可以重写Build以使用异步API,然后直接从事件处理程序调用它,而不将其包装在Task.Run