Отмена Группы Задач
Вчерашний пост вызвал в чате обсуждение. Возник такой вопрос: если одна из ожидаемых задач выбросит исключение, можно ли отменить остальные? Уточню вводные:
- задачи формально не связаны, но результат нужен или их всех, или никакого;
- задачи нужно выполнять параллельно.
Первой моей идеей было передать во все задачи один токен отмены, и в случае исключения отменять все задачи одним токеном. Проблема в том, что сам токен не позволяет инициировать отмену (он только реагирует на отмену), поэтому в задачи нужно передавать
CancellationTokenSource. В итоге, поискав решение, я нашёл интересный пост на StackOverflow, кратким описанием которого хочу поделиться. Извините за однобуквенные имена, сокращал, как мог. Полный код по ссылке ниже.Итак, у нас есть «хорошие» и «плохие» задачи (передаём в них время задержки delay для удобства тестирования):
async Task<object> DoAsync(string id,Следующий метод «оборачивает» задачу, перехватывая исключения в ней и вызывая отмену остальных:
CancellationToken token, int delay)
{
await Task.Delay(delay, token);
return id;
}
async Task<object> BadAsync(string id,
CancellationToken token, int delay)
{
await Task.Delay(delay, token);
throw new Exception(id);
}
async Task<T> Wrap<T>(Task<T> task,Теперь собственно метод, выполняющий все задачи:
CancellationTokenSource cts)
{
try {
return await task;
}
catch {
if (!cts.IsCancellationRequested)
cts.Cancel(); // отменяем остальные
throw;
}
}
public async Task DoWorkAsync(CancellationToken t)Здесь мы используем внешний токен отмены
{
var tasks = new List<Task<object>>();
var cts = new CancellationTokenSource();
var ct = cts.Token;
try {
using (t.Register(() => cts.Cancel())) {
// параллельно запускаем задачи
tasks.Add(Wrap(DoAsync("1", ct, 500), cts));
tasks.Add(Wrap(DoAsync("2", ct, 1000), cts));
tasks.Add(Wrap(DoAsync("3", ct, 1500), cts));
tasks.Add(Wrap(BadAsync("Bad", ct, 700), cts));
await Task.WhenAll(tasks.ToArray());
}
}
catch (Exception e) {
Console.WriteLine(e.Message);
}
Console.WriteLine("Results:");
tasks.ForEach((task) =>
Console.WriteLine(task.Status));
}
t, чтобы иметь возможность отменить все задачи извне. В директиве using регистрируем действие, которое будет выполнено при отмене внешнего токена.Далее создаём несколько задач, «оборачивая» их в наш метод
Wrap. Передаём в них один токен отмены и разные времена задержки, имитируя асинхронную работу. Наша «плохая» задача выбросит исключение после задачи 1, но перед завершением задач 2 и 3, метод Wrap перехватит исключение и отменит задачи 2 и 3.Запускаем:
var cts = new CancellationTokenSource(10000);Подробный код и обработка различных вариантов завершения группы задач по ссылке ниже.
DoWorkAsync(cts.Token).Wait();
//Вывод (в порядке регистрации задач):
RanToCompletion
Canceled
Canceled
Faulted
Источник: https://stackoverflow.com/questions/19394150/tpl-execute-dependent-task-and-stop-all-task-when-some-task-fails#19397190