2015-05-22 2 views
4

Когда я использую функцию Task.WhenAll(), и исключение отправляется в Задачу, генерируется новое исключение AggregateException, я могу поймать его, чтобы увидеть все исключения, которые произошли в Задания. Однако, когда я использую Task.WhenAny(), исключение не выбрасывается. Вместо этого я должен проверить свойство Task.Exception на значение, чтобы увидеть, произошло ли исключение. Это похоже на плохой запах кода, поскольку я должен был бы не забыть проверить свойство Task.Exception каждый раз, когда я использую Task.WhenAny(). Разве не должно быть лучшего способа?Как обрабатывать исключения при использовании Task Parallel Library Task.WhenAny()

Вот пример того, что я имею в виду:

private async void btnMultipleExceptions_Click(object sender, EventArgs e) { 
     var task1 = ThrowNotImplementedException(); 
     var task2 = ThrowDivideByZeroException(); 

     try { 
      Task task = await Task.WhenAny(task1, task2); 

      // Even if an exception is thrown in one of the tasks (in our case, 
      // task1 will throw first) no exception is thrown from 
      // the above await Task.WhenAny(). Instead, the exception is placed on the 
      // Task.Exception property. So I need to check for it every time 
      // I call Task.WhenAny()? 
      if (task.Exception != null) { 
       Console.WriteLine("Exceptions: " + string.Join(Environment.NewLine, 
        task.Exception.InnerExceptions.Select(x => x.Message).ToArray())); 
      } else { 
       Console.WriteLine("No Exceptions!"); 
      } 
     } catch(Exception ex) { 
      // Try to catch all exceptions??? 
      AggregateException allEx = ex as AggregateException; 

      if (allEx != null) { 
       Console.WriteLine("Exceptions: " + string.Join(Environment.NewLine, 
        allEx.InnerExceptions.Select(x => x.Message).ToArray())); 
      } else { 
       Console.WriteLine("Exceptions: " + ex.Message); 
      } 
     } 
    } 

    private async Task ThrowNotImplementedException() { 
     await Task.Delay(TimeSpan.FromSeconds(1)); 
     throw new NotImplementedException(); 
    } 

    private async Task ThrowDivideByZeroException() { 
     await Task.Delay(TimeSpan.FromSeconds(2)); 
     throw new DivideByZeroException(); 
    } 

ответ

2

Просто await задача вернулся из WhenAny. Если он ошибочен, он разворачивает исключение и бросает его, если это не так, вы знаете, что это уже сделано, чтобы продолжить.

В качестве альтернативы, вы можете просто позвонить Unwrap по телефону Task.WhenAny, а затем await. Это будет семантически идентично предыдущему варианту; это только разница в том, считаете ли вы, что это более или менее ясно.

Если вы нашли себя очень часто разворачивает результат WhenAny вы могли бы просто написать свои собственные реализации, разворачивать задачу для вас, и использовать их вместо:

public static Task WhenAny(IEnumerable<Task> tasks) 
{ 
    return Task.WhenAny(tasks).Unwrap(); 
} 
public static Task<T> WhenAny<T>(IEnumerable<Task<T>> tasks) 
{ 
    return Task.WhenAny(tasks).Unwrap(); 
} 
//TODO add wrappers for the `params` overloads if you want them too 
+0

Благодарим вас за расширения. К сожалению, я думаю, что я все еще не вижу ничего очевидного. Если я использую 'waitait Task.WhenAny (task1, task2);' и следующая строка 'Console.WriteLine (« Без исключений »),' 'Console.WriteLine();' выполняется. Обработчик улова - нет. Есть ли что-то еще, что мне нужно делать при использовании 'Task.WhenAny()'? –

+0

@HenryDaehnke Когда вы говорите 'ждут Task.WhenAny (task1, task2);' который разрешает 'Task', потому что' WhenAny' возвращает 'Task ', поэтому его ждет 'Task'. Если вы 'await' it * снова * (или разворачиваете его перед его ожиданием), то вы ожидаете, что эта действительная задача сработала, а выполнение перейдет в блок' catch'. В этом весь ответ, вам нужно ждать внутренней задачи, а не внешней задачи. – Servy

+0

Хорошо, это действительно странно. По-видимому, «Task.WhenAny() не перебрасывает исключение, когда вы« ждете »его. Вместо этого вы можете «ждать» 'await' (т. Е.« Ждать ждут Task.WhenAny (task1, task2) »). Получил ответ от [link] (http://www.tomdupont.net/2014/09/await-await-taskwhenany.html). –

2

Хотя принятый ответ даст вам исключение из Задачи, которая была заполнена сначала. Исключение в 2-й задаче все равно будет выбрано, а в версиях .NET до 4.5 оно будет эскалировано на уровне нити, когда оно получит GC'd, если оно будет незаметно. Наблюдение и регистрация нефатальных исключений задач - превосходное использование для Continuations (я полагаю, из вашего сценария WhenAny(), что это не фатальное условие, если одна из ваших задач выходит из строя). Рассмотрим что-то вроде этого:

private static void LogIfErrors(Task source) 
{ 
    if(source.Exception == null) return; 
    source.Exception.Handle(ex => 
    { 
     Log.Error("#unhandled #task #error", ex); 
     return true; 
    }); 
    return; 
} 

private void DoStuff() 
{ 
    // note that you cannot inline the ContinueWith() statement, 
    // because it would change the value of task1 to hold your 
    // continuation instead of your parent task 

    var task1 = ThrowNotImplementedException(); 
    task1.ContinueWith(LogIfErrors, TaskContinuationOptions.OnlyOnFaulted); 

    var task2 = ThrowDivideByZeroException(); 
    task2.ContinueWith(LogIfErrors, TaskContinuationOptions.OnlyOnFaulted); 

    var firstCompleted = await Tasks.WhenAny(task1, task2).Unwrap(); 

} 

Теперь, даже если одна из ваших задач давно работает и исключение долго после того, как ваш первый к финишу задача была возвращена WhenAny(), вы все еще есть шанс наблюдать и исключение и (в < = .NET 4.0) не позволяют убить объединенный поток.

+0

Все это становится спорным с .NET 4.5, в котором незаметные исключения игнорируются, а не срывают поток. – Servy

+0

Хорошая точка, пересмотрены для ясности. Даже в моем 4.5 приложений. Я по-прежнему использую эту модель довольно религиозно, чтобы в моих журналах появлялись исключения для задач «Огонь и забыть»! – Dusty

+0

@Dusty Даже не знал о потенциальной проблеме с незаметными исключениями. И это хороший момент для их регистрации, даже если они больше не срывают поток в .NET 4.5. –

Смежные вопросы