В связи с этим ответом,

Если я действительно хочу «запустить и забыть» метод, который действительно возвращает задачу, и (для простоты) предположим, что этот метод не будет генерировать какие-либо исключения. Я могу использовать метод расширения, указанный в ответе:

public static void Forget(this Task task)
{
}

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

Вопрос: не было бы более подходящим в этом сценарии, чтобы метод расширения имел форму:

public static async void Forget(this Task task)
{
    await task;
}

Так что ошибки программирования вызывают исключение и обостряются (обычно останавливая процесс).

В случае метода с ожидаемыми (и игнорируемыми) исключениями, этот метод должен стать более сложным (в стороне, любые предложения о том, как создать версию этого метода, которая будет принимать список приемлемых и игнорируемых типов исключений? )

42
Matt Smith 4 Апр 2014 в 17:34
Для идей о том, как игнорировать исключения задач, посмотрите это: Простой способ игнорирования определенных типов исключений при ожидании задачи
 – 
Theodor Zoulias
5 Май 2020 в 08:54

3 ответа

Лучший ответ

Это зависит от желаемой семантики. Если вы хотите, чтобы исключения были замечены, то да, вы можете await задачу. Но в этом случае это не совсем «выстрелил и забыл».

Настоящее «выстрелил и забыл» - в том смысле, что вас не волнует, когда он завершится, успешно или с ошибкой - встречается крайне редко.

Изменить:

Для обработки исключений:

public static async void Forget(this Task task, params Type[] acceptableExceptions)
{
  try
  {
    await task.ConfigureAwait(false);
  }
  catch (Exception ex)
  {
    // TODO: consider whether derived types are also acceptable.
    if (!acceptableExceptions.Contains(ex.GetType()))
      throw;
  }
}

Обратите внимание, что я рекомендую использовать await вместо ContinueWith. ContinueWith имеет удивительный планировщик по умолчанию (как отмечалось в моем блоге ) и Task.Exception заключат фактическое исключение в AggregateException, делая код обработки ошибок более громоздким.

52
Stephen Cleary 4 Апр 2014 в 19:00
4
Итак, как мне понять семантику, что меня не волнует, когда что-то закончится, но я забочусь об исключениях? Возможно ли иметь эту семантику без async void на каком-то уровне?
 – 
Matt Smith
4 Апр 2014 в 17:57
2
Ты прикидываешь или я не понимаю? Похоже, что try/catch - это то, что вам нужно. Отредактировано, чтобы показать пример кода.
 – 
Stephen Cleary
4 Апр 2014 в 18:50
1
И Стивен, Стивен, твое редактирование выглядит именно так, как я хочу. Спасибо. И да, как Нозерацио, я планирую использовать это как универсальную заглушку для «выстрелил-забыл» (кроме исключений «не забывать»/проглатывать.
 – 
Matt Smith
4 Апр 2014 в 19:35
5
Блок catch не будет выполняться в захваченном контексте (поэтому никакое ведение журнала или что-то еще не будет), но если исключение неприемлемо и повторно сгенерировано (throw;), тогда да, исключение будет ( ре-)рейз на SynchronizationContext, который был активен в начале Forget.
 – 
Stephen Cleary
4 Апр 2014 в 20:36
3
Он вызывает Post, на самом деле .
 – 
Stephen Cleary
5 Апр 2014 в 04:30

В связанном вопросе я изначально хотел использовать static void Forget(this Task task) в следующем контексте:

var task = DoWorkAsync();
QueueAsync(task).Forget();

// ...

async Task QueueAsync(Task task)
{
    // keep failed/cancelled tasks in the list
    // they will be observed outside
    _pendingTasks.Add(task);
    await task;
    _pendingTasks.Remove(tasks)
}

Это выглядело великолепно, но потом я понял, что фатально исключения, которые могут быть сгенерированы _pendingTasks.Add / _pendingTasks.Remove, пропадут незамеченными и потеряны, что нехорошо.

Поэтому я просто сделал QueueTask методом async void, чем он и является:

var task = DoWorkAsync();
QueueAsync(task);

// ...

async void QueueAsync(Task task)
{
    // keep failed/cancelled tasks in the list
    // they will be observed outside
    _pendingTasks.Add(task);
    try
    {
        await task;
    }
    catch
    {
        return;
    }
    _pendingTasks.Remove(tasks)
}

Хотя мне не нравится пустой catch {}, я думаю, что здесь есть смысл.

В этом сценарии сохранение async Task QueueAsync() и использование async void Forget(this Task task), как вы предлагаете, было бы излишним, ИМО.

4
Community 23 Май 2017 в 15:32

Да, если вас интересует, вызывает ли задача исключение, тогда вам нужно await получить результат, но, с другой стороны, это в значительной степени противоречит цели "запустить и забыть ". ".

В сценарии, когда вы хотите узнать, произошло ли что-то плохое , рекомендуется использовать продолжение, например

public static void ForgetOrThrow(this Task task)
{
    task.ContinueWith((t) => {
        Console.WriteLine(t.Exception);
    }, TaskContinuationOptions.OnlyOnFaulted);
}
3
James 4 Апр 2014 в 18:37
«если вы сделаете Забудьте об асинхронном методе, то вы в основном повторно представите ту же проблему»? Что вы имеете в виду? Он подавляет предупреждение и имеет такое же поведение в случае отсутствия исключений. Думаю, я хочу "Выстрелил и забыл", но я не хочу игнорировать ошибки.
 – 
Matt Smith
4 Апр 2014 в 17:53
Это подавляет предупреждение? Предупреждение связано с тем, что извне нет await вызова WorkAsync() из метода async. Метод расширения Forget (который ничего не делает) подавляет предупреждение, поскольку это неасинхронный метод. Повторное выполнение async должно повторно ввести предупреждение, поскольку вы вернулись к вызову метода async, а не к вызову await (если только методы расширения не обрабатываются по-другому ...).
 – 
James
4 Апр 2014 в 17:58
3
Это async void, так что ждать нечего.
 – 
Matt Smith
4 Апр 2014 в 17:59
1
Албахари предложил ту же идею в главе 23 C# 5.0 In a Nutshell как способ «проглотить» необработанное исключение задачи, но отметив, что вы можете что-то сделать с исключением, например, зарегистрировать его... так что я думаю, что это могло бы быть расширяемым до вашей идеи игнорировать одни и повторно бросать другие. Вместо того, чтобы просто throw t.Exception, вы могли бы сначала "наблюдать" за ним (var ignore = t.Exception; и, если он не является приемлемым, тогда бросить его: if(reallyBad) {throw t.Exception;} Это позволяет приемлемым просто проглотить.
 – 
mdisibio
4 Апр 2014 в 18:48
Арх, но в этом и заключается сложность. Вы не хотите просто повторно вызывать исключение в задаче продолжения, тогда вы замените одно необработанное исключение другим. Вам нужно каким-то образом отправить его обратно в контекст синхронизации, как, по-видимому, достигается предложение Стивена Клири.
 – 
Søren Boisen
3 Июн 2015 в 11:45