如何正确等待异步委托?

时间:2019-05-03 07:23:01

标签: c# asynchronous async-await delegates

在同步世界中,我有一个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()与异步委托一起使用:

(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。所有操作都停止,但未处理异常。只捕获没有委托的作品:

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

这意味着我不能使用(1)到(5)中的任何形式的TryExecute()来重用try / catch / log逻辑,我只能在(0)之类的任何地方重复try / catch / log

我的整个控制台代码如下:

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 个答案:

答案 0 :(得分:1)

呼叫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);
    }
}

答案 1 :(得分:0)

  

我不知道如何将其重写为异步/等待模式。

转换为async时,第一步是转换方法调用。在这种情况下,应首先将委托转换为异步兼容的委托。

Action是一个不带参数且没有返回值的委托,例如void Method()。没有参数且没有返回值的异步方法看起来像async Task Method(),所以its delegate type would be Func<Task>

旁注:与代表打交道时要记住async void is unnatural and should be avoided

一旦您将委托类型从Action更改为Func<Task>,就可以await返回值,这将使您的TryExecute方法更改为{{1} },例如:

async Task
  

以上五种方法均不能捕获Exception。所有操作都因未处理的异常而停止。

这实际上只是在调试器中运行代码的副作用。使用异步代码,有时您会看到实际上未处理的“未处理”异常。这是因为是由编译器生成的代码正在捕获异常并将其放置在任务上,稍后您的代码private static async Task TryExecuteAsync(Func<Task> asyncAction) { try { await asyncAction(); } catch (Exception ex) { Log(ex); throw; } } 将其重新引发,然后代码await将其重新引发。当原始异常被您的代码以外的其他东西捕获(由编译器生成的代码捕获)时,调试器会感到有些惊讶,并且无法知道这是完全正常的。

因此,如果您继续跳过调试器的“未处理”异常,您会发现它工作得很好。