Async/await for long-running API methods with progress/cancelation

♀尐吖头ヾ 提交于 2019-12-18 16:53:08

问题


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?


回答1:


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.



来源:https://stackoverflow.com/questions/13033258/async-await-for-long-running-api-methods-with-progress-cancelation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!