Пожар и Забытый подход
Связанный с этим ответом,
Если я действительно хочу "Огонь и забыть" метод, возвращающий задачу, и (для простоты) допустим, что метод не должен бросать какие-либо исключения. Я могу использовать метод расширения, указанный в ответе:
public static void Forget(this Task task)
{
}
Используя этот подход, если есть ошибки в действии Task
, которые вызывают исключение, которое вызывается, тогда, когда возникает неожиданное исключение, исключение будет проглочено и останется незамеченным.
Вопрос: Не было бы более уместным в этом сценарии для того, чтобы метод расширения имел вид:
public static async void Forget(this Task task)
{
await task;
}
Так что ошибки программирования вызывают исключение и получают эскалацию (обычно приводящий к снижению процесса).
В случае метода с ожидаемыми (и неосведомленными) исключениями, метод должен стать более сложным (в качестве исключения, любые предложения о том, как построить версию этого метода, которая будет принимать список приемлемых и неосведомленных типы исключений?)
Ответы
Ответ 1
Это зависит от семантики, которую вы хотите. Если вы хотите, чтобы исключения были замечены, тогда да, вы могли бы await
выполнить задачу. Но в этом случае это не по-настоящему "огонь и забыть".
Истинный "огонь и забыть" - в том смысле, что вам неинтересно, когда он завершается или успешно завершается или с ошибкой, - крайне редко.
Edit:
Для обработки исключений:
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
, что делает код обработки ошибок более громоздким.
Ответ 2
Да, если вас интересовало, вызвала ли исключение исключение, то вам нужно будет await
результат, но на флипсайд, который в значительной степени побеждает цель "огонь и забыть".
В сценарии, в котором вы хотите узнать, произошло ли что-то плохое, рекомендуемый способ сделать это - использовать продолжение, например.
public static void ForgetOrThrow(this Task task)
{
task.ContinueWith((t) => {
Console.WriteLine(t.Exception);
}, TaskContinuationOptions.OnlyOnFaulted);
}
Ответ 3
В связанном вопросе я сначала хотел использовать 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
a 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)
, как вы предлагаете, будут перегружены, IMO.