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