24

.NET 4.0を使用した並列プログラミングに関して、私が何をしているのかは明らかにわかりません。私は、いくつかの無意識の作業を行うタスクを開始する単純なWindowsアプリを持っています(数値1〜1000を出力します)。長時間実行されるプロセスをシミュレートするために、途中でかなりの一時停止を行いました。この長い一時停止が行われているときに、[停止]ボタンを押すと、そのイベントハンドラーがCancellationTokenSourceのCancelメソッドを呼び出します。キャンセルされたタスクが現在の反復で完了するまで、[停止]ボタンのイベントハンドラーでそれ以上の処理(この場合はメッセージの出力)を実行したくありません。どうすればよいですか?[停止]ボタンのイベントハンドラーでTask.WaitAllなどを使用しようとしましたが、未処理のAggregateExceptionがスローされます。上記のように実行した場合に私の問題を説明するのに役立つコードは次のとおりです。

  private Task t;
  private CancellationTokenSource cts;

  public Form1()
  {
     InitializeComponent();
  }

  private void startButton_Click(object sender, EventArgs e)
  {
     statusTextBox.Text = "Output started.";

     // Create the cancellation token source.
     cts = new CancellationTokenSource();

     // Create the cancellation token.
     CancellationToken ct = cts.Token;

     // Create & start worker task.
     t = Task.Factory.StartNew(() => DoWork(ct), ct);
  }

  private void DoWork(CancellationToken ct)
  {
     for (int i = 1; i <= 1000; i++)
     {
        ct.ThrowIfCancellationRequested();

        Thread.Sleep(10);  // Slow down for text box outout.
        outputTextBox.Invoke((Action)(() => outputTextBox.Text = i + Environment.NewLine));

        if (i == 500)
        {
           Thread.Sleep(5000);
        }
     }
  }

  private void stopButton_Click(object sender, EventArgs e)
  {
     cts.Cancel();

     Task.WaitAll(t);  // this doesn't work :-(

     statusTextBox.Text = "Output ended.";
  }

  private void exitButton_Click(object sender, EventArgs e)
  {
     this.Close();
  }

これに関する助けをいただければ幸いです。前もって感謝します。

4

2 に答える 2

10

単一のタスクであるため、通常はTask.Wait(の代わりに)を使用します。WaitAll次に、例外を適切に処理しました。

private void stopButton_Click(object sender, EventArgs e)
{
    cts.Cancel();
    try
    {
        t.Wait();  // This will throw
    }
    catch (AggregateException ae)
    {
       ae.Handle<OperationCanceledException>(ce => true);
    }

    statusTextBox.Text = "Output ended.";
}

をキャンセルするTaskと、OperationCanceledExceptionはにラップされ、タスクを呼び出すか取得しようとするAggregateExceptionとすぐにスローされます(がの場合)。Wait()ResultTask<T>


純粋にあなたの情報のために-これは、特にここで行っていることを考えると、C#5が物事を単純化する1つの場所です。新しい非同期サポートを使用すると、次のように記述できます。

// No need for "t" variable anymore 
// private Task t;


private async void startButton_Click(object sender, EventArgs e)
{
   statusTextBox.Text = "Output started.";

   // Create the cancellation token source.
   cts = new CancellationTokenSource();

   try
   {
      // Create & start worker task.
      await Task.Run(() => DoWork(cts.Token));
      statusTextBox.Text = "Output ended.";
   }
   catch(OperationCanceledException ce) 
   {
      // Note that we get "normal" exception handling
      statusTextBox.Text = "Operation canceled.";
   }
}

private void stopButton_Click(object sender, EventArgs e)
{
   // Just cancel the source - nothing else required here
   cts.Cancel();
}
于 2012-08-17T00:16:43.337 に答える
4

申し訳ありませんが、その回答にコメントとして質問を追加するだけの評判は十分ではありません。回避策がどのように答えと見なされるかを尋ねたかったのです。

回答のコードは、ユーザーが一度に複数のバックグラウンドプロセスを開始しないようにするために、ある種のユーザーインターフェイスの調整に依存していませんか?もちろん、それは常に問題です。

ここでは、述べられている質問に答える代替案を提示します。キャンセルリクエストの終了を待つ方法を示しています。これは、適切に管理されていない場合でもUIを台無しにする方法でこれを行いますが、本当に必要な場合は、キャンセル後に実際に待機するコードがあります。これは、より大きなC#クラスからの抜粋です。

AutoResetEvent m_TaskFinishedEvent = new AutoResetEvent( false );
private IAsyncAction m_Operation = null;

private Task WaitOnEventAsync( EventWaitHandle WaitForEvent )
{
    return Task.Run( () => { WaitForEvent.WaitOne(); } );
}

public async void Start()
{
if( m_Operation != null )
    {
        // Cancel existing task
        m_Operation.Cancel();
        // Wait for it to finish. This returns control to the UI.
        await WaitOnEventAsync( m_TaskFinishedEvent );
    }
    // Start the background task.
    m_Operation = ThreadPool.RunAsync( WorkerMethod );
}

private void WorkerMethod( IAsyncAction operation )
{
    while( m_Operation.Status != AsyncStatus.Canceled )
        ; // Add real work here.

    m_TaskFinishedEvent.Set();
}

このコードは、イベントオブジェクトに依存して、タスクがほぼ終了したことを通知します。WorkerMethod()コードはまだ返されていませんが、イベントが通知されると、すべての有用な作業が実行されます。

このコードの使用方法のため、Stop()関数を提供しませんでした。待機を行うコードは、それがコードの動作方法である場合、そのStop()関数に入るだけです。

はい、キャンセル例外のため、通常のWait()関数を使用することはできません。しかし、受け入れられた答えは、より回避策であり、不快感はありません(そしておそらく私は間違っていますか?)。

于 2013-10-03T22:57:20.927 に答える