7

編集 await にワーカーを非同期的に呼び出すよう強制する適切な方法は、次のように 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からいくつかの光を得ました。


これは簡単なはずですが、私は非同期/待機が初めてなので、ご容赦ください。長時間実行される操作で 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 で単純な長時間実行メソッドを作成する方法を次に示します。ここでは、実際のプロセスをエミュレートするために、ファイルを 1 行ずつ読み取っています。実際のプロセスでは、いくつかの処理でファイル形式を変換する必要があります。


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 が事実上ブロックされており、進行が見られません。では、そのようなシナリオの全体像は、消費するコードに関してどのようになるでしょうか?

4

1 に答える 1

9

そのブログ投稿の上部に記載されているように、その投稿の情報は古くなっています。IProgress<T>.NET 4.5 で提供される新しい API を使用する必要があります。

ブロッキング I/O を使用している場合は、コア メソッドをブロッキングします。

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

そして、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<int>((_, value) => { _progress.Value = value; });
  await Task.Run(() => builder.Build(dlg.FileName, cts.Token, progress);

  // hide the progress UI...
}

または、非同期 API を使用するように書き直しBuildてから、 でラップせずにイベント ハンドラーから直接呼び出すこともできますTask.Run

于 2012-10-24T05:48:24.310 に答える