В связи с этим ответом,
Если я действительно хочу «запустить и забыть» метод, который действительно возвращает задачу, и (для простоты) предположим, что этот метод не будет генерировать какие-либо исключения. Я могу использовать метод расширения, указанный в ответе:
public static void Forget(this Task task)
{
}
При использовании этого подхода, если есть ошибки в действии Task
, которые вызывают возникновение исключения, тогда, когда возникает непредвиденное исключение, исключение будет проглочено и останется незамеченным.
Вопрос: не было бы более подходящим в этом сценарии, чтобы метод расширения имел форму:
public static async void Forget(this Task task)
{
await task;
}
Так что ошибки программирования вызывают исключение и обостряются (обычно останавливая процесс).
В случае метода с ожидаемыми (и игнорируемыми) исключениями, этот метод должен стать более сложным (в стороне, любые предложения о том, как создать версию этого метода, которая будет принимать список приемлемых и игнорируемых типов исключений? )
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
, делая код обработки ошибок более громоздким.
async void
на каком-то уровне?
try
/catch
- это то, что вам нужно. Отредактировано, чтобы показать пример кода.
catch
не будет выполняться в захваченном контексте (поэтому никакое ведение журнала или что-то еще не будет), но если исключение неприемлемо и повторно сгенерировано (throw;
), тогда да, исключение будет ( ре-)рейз на SynchronizationContext
, который был активен в начале Forget
.
В связанном вопросе я изначально хотел использовать 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)
, как вы предлагаете, было бы излишним, ИМО.
Да, если вас интересует, вызывает ли задача исключение, тогда вам нужно await
получить результат, но, с другой стороны, это в значительной степени противоречит цели "запустить и забыть ". ".
В сценарии, когда вы хотите узнать, произошло ли что-то плохое , рекомендуется использовать продолжение, например
public static void ForgetOrThrow(this Task task)
{
task.ContinueWith((t) => {
Console.WriteLine(t.Exception);
}, TaskContinuationOptions.OnlyOnFaulted);
}
await
вызова WorkAsync()
из метода async
. Метод расширения Forget
(который ничего не делает) подавляет предупреждение, поскольку это неасинхронный метод. Повторное выполнение async
должно повторно ввести предупреждение, поскольку вы вернулись к вызову метода async
, а не к вызову await
(если только методы расширения не обрабатываются по-другому ...).
async void
, так что ждать нечего.
throw t.Exception
, вы могли бы сначала "наблюдать" за ним (var ignore = t.Exception;
и, если он не является приемлемым, тогда бросить его: if(reallyBad) {throw t.Exception;}
Это позволяет приемлемым просто проглотить.
Похожие вопросы
Связанные вопросы
Новые вопросы
c#
C# (произносится как «see Sharp») — это высокоуровневый мультипарадигменный язык программирования со статической типизацией, разработанный Microsoft. Код C# обычно нацелен на семейство инструментов и сред выполнения Microsoft .NET, которое включает в себя .NET, .NET Framework, .NET MAUI и Xamarin среди прочих. Используйте этот тег для ответов на вопросы о коде, написанном на C#, или о формальной спецификации C#.