web-dev-qa-db-ja.com

Task.WaitAllと例外

例外処理と並列タスクに問題があります。

以下に示すコードは2つのタスクを開始し、それらが完了するまで待機します。私の問題は、タスクが例外をスローした場合、キャッチハンドラーに到達しないことです。

        List<Task> tasks = new List<Task>();
        try
        {                
            tasks.Add(Task.Factory.StartNew(TaskMethod1));
            tasks.Add(Task.Factory.StartNew(TaskMethod2));

            var arr = tasks.ToArray();                
            Task.WaitAll(arr);
        }
        catch (AggregateException e)
        {
            // do something
        }

ただし、次のコードを使用してタイムアウト付きのタスクを待機すると、例外がキャッチされます。

 while(!Task.WaitAll(arr,100));

WaitAllのドキュメントには、私の最初の試みが正しいものであると記載されているため、何か不足しているようです。それが機能しない理由を理解するのを助けてください。

28
thumbmunkeys

これを再現できません-私にとってはうまくいきます:

using System;
using System.Threading;
using System.Threading.Tasks;

class Test
{
    static void Main()
    {
        Task t1 = Task.Factory.StartNew(() => Thread.Sleep(1000));
        Task t2 = Task.Factory.StartNew(() => {
            Thread.Sleep(500);
            throw new Exception("Oops");
        });

        try
        {
            Task.WaitAll(t1, t2);
            Console.WriteLine("All done");
        }
        catch (AggregateException)
        {
            Console.WriteLine("Something went wrong");
        }
    }
}

それは私が期待するとおり、「何か問題が発生しました」と出力します。

タスクの1つが完了していない可能性はありますか? WaitAllは、一部のタスクがすでに失敗した場合でも、実際にはすべてのタスクが完了するまで待機します。

26
Jon Skeet

私の回答/質問(上記)のコメントで示唆されているように、問題を解決する方法は次のとおりです。

呼び出し元は、バリアによって調整されているタスクによって発生した例外をキャッチし、強制キャンセルによって他のタスクに通知します。

CancellationTokenSource cancelSignal = new CancellationTokenSource();
try
{
    // do work
    List<Task> workerTasks = new List<Task>();
    foreach (Worker w in someArray)
    {
        workerTasks.Add(w.DoAsyncWork(cancelSignal.Token);
    }
    while (!Task.WaitAll(workerTasks.ToArray(), 100, cancelSignal.Token)) ;

 }
 catch (Exception)
 {
     cancelSignal.Cancel();
     throw;
 }
11
gap

コレクション内の各アイテムの呼び出しを作成しようとしていましたが、次のようなことが判明しました。

var parent = Task.Factory.StartNew(() => {
  foreach (var acct in AccountList)
    {
      var currAcctNo = acct.Number;
      Task.Factory.StartNew(() =>
      {
        MyLocalList.AddRange(ProcessThisAccount(currAcctNo));
      }, TaskCreationOptions.AttachedToParent);
      Thread.Sleep(50);
    }
  });

子タスクを追加するたびにThread.Sleepを追加する必要がありました。追加しなかった場合、プロセスはcurrAcctNoを次の反復で上書きする傾向があるためです。私のリストには3つまたは4つの異なるアカウント番号があり、それぞれを処理すると、ProcessThisAccount呼び出しはすべての呼び出しの最後のアカウント番号を表示します。スリープを入れたら、プロセスはうまくいきます。

0
Keith Pinster