Выбрасывание немедленно из асинхронного метода - программирование

Выбрасывание немедленно из асинхронного метода

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

Я могу придумать случаи, когда я могу немедленно бросить. Вот пример:

public static async Task TestExAsync(string filename)
{
    // the file is missing, but it may be there again
    // when the exception gets observed 5 seconds later,
    // hard to debug

    if (!System.IO.File.Exists(filename))
        throw new System.IO.FileNotFoundException(filename);

    await Task.Delay(1000);
}

public static void Main()
{
    var task = TestExAsync("filename");
    try
    {
        Thread.Sleep(5000); // do other work
        task.Wait(); // wait and observe
    }
    catch (AggregateException ex)
    {
        Console.WriteLine(new { ex.InnerException.Message, task.IsCanceled });
    }
    Console.ReadLine();
}

Я мог бы использовать async void, чтобы обойти это, которое сразу бросается:

// disable the "use await" warning
#pragma warning disable 1998
public static async void ThrowNow(Exception ex)
{
    throw ex;
}
#pragma warning restore 1998

public static async Task TestExAsync(string filename)
{
    if (!System.IO.File.Exists(filename))
        ThrowNow(new System.IO.FileNotFoundException(filename));

    await Task.Delay(1000);
}

Теперь я могу обработать это исключение прямо на месте с помощью Dispatcher.UnhandledException или AppDomain.CurrentDomain.UnhandledException, по крайней мере, сразу же принести его вниманию пользователя.

Есть ли другие варианты для этого сценария? Возможно, это надуманная проблема?

4b9b3361

Ответ 1

Если вы действительно хотите это сделать, вы можете использовать тот же подход Jon Skeet используемый в его повторной реализации LINQ: создать синхронный метод, который может вызвать или вызвать реальный асинхронный метод:

public static Task TestExAsync(string filename)
{
    if (!System.IO.File.Exists(filename))
        throw new System.IO.FileNotFoundException(filename);

    return TestExAsyncImpl(filename);
}

private static async Task TestExAsyncImpl(string filename)
{
    await Task.Delay(1000);
}

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

Ответ 2

Я считаю, что нормальное поведение уместно. Ваш поток зависит от результата функции async для выполнения следующей обработки, поэтому исключение должно быть выбрано в вашем потоке. Затем ваш код потока может принять надлежащую меру для восстановления из исключения. Поскольку вы можете передавать свои задания и инициировать множество задач, ваш код восстановления может находиться в где-то еще, где вам нужно получить результат задачи, чем ваш исходный код. Если исключение сразу же выбрано, оно может быть сброшено вне вашего кода восстановления.

Функция void asynch сразу бросает вызов, что имеет смысл, потому что ничто не зависит от его результата, и нет задачи, которую нужно выполнить.

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