web-dev-qa-db-fra.com

Task.WaitAll et exceptions

J'ai un problème avec la gestion des exceptions et les tâches parallèles.

Le code présenté ci-dessous commence 2 tâches et attend leur fin. Mon problème est que, dans le cas où une tâche lève une exception, le gestionnaire de capture n'est jamais atteint.

        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
        }

Toutefois, lorsque j'utilise le code suivant pour attendre les tâches avec un délai d'attente, l'exception est interceptée. 

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

Il me semble manquer quelque chose, car la documentation de WaitAll décrit ma première tentative d’être la bonne. Aidez-moi à comprendre pourquoi cela ne fonctionne pas.

27
thumbmunkeys

Je ne peux pas reproduire ceci - cela fonctionne bien pour moi:

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

Cela affiche "Quelque chose a mal tourné" comme je m'y attendais.

Est-il possible que l'une de vos tâches ne soit pas terminée? WaitAll attend vraiment que toutes les tâches soient terminées, même si certaines ont déjà échoué.

25
Jon Skeet

Voici comment j'ai résolu le problème, comme mentionné dans les commentaires sur ma réponse/question (ci-dessus): 

L'appelant intercepte les exceptions générées par les tâches coordonnées par la barrière et signale les autres tâches avec une annulation forcée:

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

J'essayais de créer un appel pour chaque élément d'une collection, qui s'est avéré être comme ceci:

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

Je devais ajouter Thread.Sleep après chaque ajout d'une tâche enfant, sinon le processus aurait tendance à écraser currAcctNo à la prochaine itération. J'aurais 3 ou 4 numéros de compte distincts dans ma liste, et lors du traitement de chacun, l'appel ProcessThisAccount afficherait le dernier numéro de compte pour tous les appels. Une fois que je mets le sommeil dedans, le processus fonctionne très bien.

0
Keith Pinster