Подтвердить что ты не робот

Task.WaitAll и исключения

У меня проблема с обработкой исключений и параллельными задачами.

Приведенный ниже код запускает 2 задания и ждет их завершения. Моя проблема в том, что если задача выдает исключение, обработчик catch никогда не будет достигнут.

        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 описывает мою первую попытку быть правильной. Пожалуйста, помогите мне понять, почему он не работает.

4b9b3361

Ответ 1

Невозможно воспроизвести это - он отлично работает для меня:

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

Что печатает "Что-то пошло не так", как я ожидал.

Возможно ли, что одна из ваших задач еще не закончена? WaitAll действительно ждет завершения всех задач, даже если некоторые из них уже сработали.

Ответ 2

Вот как я решил проблему, о чем упоминалось в комментариях к моему ответу/вопросу (выше):

Вызывающий абонент ловит любые исключения, вызванные задачами, координируемыми барьером, и сигнализирует о других задачах с принудительной отменой:

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

Ответ 3

Я пытался создать вызов для каждого элемента в коллекции, который получился примерно таким:

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 будет отображаться последний номер учетной записи для всех вызовов. Как только я включу "Сон", процесс отлично работает.