Как правильно ожидать асинхронного делегата? - PullRequest
2 голосов
/ 03 мая 2019

Находясь в мире синхронизации, у меня есть функция TryExecute, чтобы обернуть логику try / catch / log для повторного использования, например:

TryExecute(() => SyncFunction());

private static void TryExecute(Action action)
{
    try
    {
        action();
    }
    catch (Exception ex)
    {
        Log(ex);
        throw;
    }
}

Я не понимаю, как переписать ее в шаблон асинхронности / ожидания.

Как я понимаю, у меня есть пять допустимых способов переписать его в async / await (не обращайте внимания на любые другие Visual Studio с предупреждением).

Использование исходной синхронизации TryExecute() с делегатом async:

(1) TryExecute(async () => await AsyncFunction());

Кажется, он больше не ждет, TryExecute() проходит без ожидания AsyncFunction() до конца.

Перезаписывает на новую синхронизацию TryExecuteTask() возвращает Task, вызывает его с или безасинхронный делегат:

(2) await TryExecuteTask(() => AsyncFunction());
(3) await TryExecuteTask(async () => await AsyncFunction());

private static Task TryExecuteTask(Func<Task> asyncAction)
{
    try
    {
        return asyncAction();
    }
    catch (Exception ex)
    {
        Log(ex);
        throw;
    }
}

Или переписать на новый асинхронный TryExecuteAsync(), вызвать его с или без асинхронного делегата:

(4) await TryExecuteAsync(() => AsyncFunction());
(5) await TryExecuteAsync(async () => await AsyncFunction());

private async static Task TryExecuteAsync(Func<Task> asyncAction)
{
    try
    {
        await asyncAction();
    }
    catch (Exception ex)
    {
        Log(ex);
        throw;
    }
}

Но если я выброшу Exception изнутри AsyncFunction(), то ни один из вышеперечисленных пяти способов не может поймать Exception.Все остановлено с необработанным исключением.Работает только catch без делегата:

(0) try
    {
        await AsyncFunction();
    }
    catch (Exception ex)
    {
        Log(ex);
    }

Это означает, что я не могу использовать любые формы TryExecute() из (1) в (5) для повторного использования логики try / catch / log, я могу только повторятьtry / catch / log везде, как (0).

Весь мой код консоли выглядит следующим образом:

class Program
{
    async static Task Main(string[] args)
    {
        // Original sync way
        TryExecute(() => SyncFunction());

        Console.WriteLine("0");
        try
        {
            await AsyncFunction();
        }
        catch (Exception ex)
        {
            Log(ex);
        }

        ////Console.WriteLine("1");
        ////TryExecute(async () => await AsyncFunction());

        ////Console.WriteLine("2");
        ////await TryExecuteTask(() => AsyncFunction());
        ////Console.WriteLine("3");
        ////await TryExecuteTask(async () => await AsyncFunction());

        ////Console.WriteLine("4");
        ////await TryExecuteAsync(() => AsyncFunction());
        ////Console.WriteLine("5");
        ////await TryExecuteAsync(async () => await AsyncFunction());

        Console.WriteLine("Finished without unhandled exception.");
    }

    private static void SyncFunction()
    {
        Console.WriteLine("SyncFunction starting");
        Thread.Sleep(500);
        Console.WriteLine("SyncFunction starting");
        throw new Exception();
    }

    private async static Task AsyncFunction()
    {
        Console.WriteLine("AsyncFunction starting");
        await Task.Run(() =>
        {

            Console.WriteLine("Sleep starting");
            Thread.Sleep(500);
            Console.WriteLine("Sleep end");
            throw new Exception();
        });
        Console.WriteLine("AsyncFunction end");
    }

    private static void TryExecute(Action action)
    {
        try
        {
            action();
        }
        catch (Exception ex)
        {
            Log(ex);
        }
    }

    private static Task TryExecuteTask(Func<Task> asyncAction)
    {
        try
        {
            return asyncAction();
        }
        catch (Exception ex)
        {
            Log(ex);
            throw;
        }
    }

    private async static Task TryExecuteAsync(Func<Task> asyncAction)
    {
        try
        {
            await asyncAction();
        }
        catch (Exception ex)
        {
            Log(ex);
            throw;
        }
    }

    private static void Log(Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

Из-за необработанного исключения я могу закомментировать только все фрагменты, кроме одного в Main() для проверки каждого случая.

Ответы [ 2 ]

1 голос
/ 03 мая 2019

Звонок await TryExecuteAsync(AsyncFunction) работает так, как вы ожидаете:

class Program
{
    async static Task Main(string[] args)
    {
        await TryExecuteAsync(AsyncFunction);
        Console.WriteLine("Finished without unhandled exception.");
    }

    private async static Task AsyncFunction()
    {
        Console.WriteLine("AsyncFunction starting");
        await Task.Run(() =>
        {

            Console.WriteLine("Sleep starting");
            Thread.Sleep(3000);
            Console.WriteLine("Sleep end");
            throw new Exception();
        });
        Console.WriteLine("AsyncFunction end");
    }

    private async static Task TryExecuteAsync(Func<Task> asyncAction)
    {
        try
        {
            await asyncAction();
        }
        catch (Exception ex)
        {
            Log(ex);
            throw;
        }
    }

    private static void Log(Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

AsyncFunction() вызывает исключение, которое регистрируется и затем перебрасывается в TryExecuteAsync. Если вы хотите перехватить вновь возникшее исключение, вы должны поставить try/catch вокруг вызова на TryExecuteAsync:

async static Task Main(string[] args)
{
    try
    {
        await TryExecuteAsync(AsyncFunction);
        Console.WriteLine("Finished without unhandled exception.");
    }
    catch (Exception ex)
    {
        Console.WriteLine("Failed to execute: " + ex.Message);
    }
}
0 голосов
/ 03 мая 2019

Я не понимаю, как переписать его в шаблон async / await.

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

Action - это делегат, который не принимает параметров и не имеет возвращаемого значения, например void Method().Асинхронный метод, который не принимает параметров и не имеет возвращаемого значения, выглядит как async Task Method(), поэтому его тип делегата будет Func<Task>.

. Примечание: это особенно важно при работе с делегатами.помнить, что async void неестественно и его следует избегать .

Как только вы измените тип вашего делегата с Action на Func<Task>, вы можете await его возвращаемое значение, котороевызывает изменение метода TryExecute на async Task следующим образом:

private static async Task TryExecuteAsync(Func<Task> asyncAction)
{
  try
  {
    await asyncAction();
  }
  catch (Exception ex)
  {
    Log(ex);
    throw;
  }
}

. Ни один из вышеперечисленных пяти способов не может перехватить исключение.Все остановлено с необработанным исключением.

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

Так что, если вы просто продолжите обходить «необработанное» исключение отладчика, вы увидите, что оно работает просто отлично.

...