Пожар и Забытый подход - программирование

Пожар и Забытый подход

Связанный с этим ответом,

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

public static void Forget(this Task task)
{
}

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

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

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

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

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

4b9b3361

Ответ 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.