使用 lambda 异步处理异常,未捕获异常

Handle exception with lambda async, exception not caught

我用 ExcecuteWithLogging 方法重构了一些 try catch 块,但是 restHandler.GetResultAsync() 中发生的异常从未被捕获,为什么?

    public async Task<Aaa> SearchByPersonnummerAsync(string personnummer)
    {
        var restHandler = new RestHandler();

        GenericResult<Aaa> aaa= await ExcecuteWithLogging(async () =>
        {
            var res = await restHandler.GetResultAsync<Aaa>(
                _configurationManager.GetSetting("api"),
                "url");

            return res;
        });

        return aaa.Result;

    }


    private T ExcecuteWithLogging<T>(Func<T> function)
    {
        try
        {
            function();

        }
        catch (Exception ex)
        {
            var message = ex.Message;

            // Log

        }
        return default(T);
    }

问题是您没有实现 ExecuteWithLogging 的重载来解决 具体 Func<Task<T>>。请记住,lambda 表达式可以转换为任何兼容的委托,这就是为什么您的异步 lambda 被成功分配给当前 ExecuteWithLogging 方法的原因。

在阅读其余答案之前,请先查看以下内容 article,当我遇到与您现在面临的问题完全相同的问题时,它对我帮助很大。我很确定在阅读那篇文章后您会理解这里发生的事情,但是为了完整起见,这里有一些示例可以使事情变得更清楚。我已按以下方式修改了您的方法:

public static async void SearchByPersonnummerAsync(string personnummer)
{
    var aaa = await ExcecuteWithLogging(async () =>
    {
        Console.WriteLine("Executing function");
        var res = await Task.Run(() =>
        {
            Thread.Sleep(100);
            Console.WriteLine("Before crashing");
            throw new Exception();
            return 1;
        });

        Console.WriteLine("Finishing execution");
        return res;
    });

}

private static T ExcecuteWithLogging<T>(Func<T> function)
{
    try
    {
        Console.WriteLine("Before calling function");
        function();
        Console.WriteLine("After calling function");
    }
    catch (Exception ex)
    {
        var message = ex.Message;
        Console.WriteLine(message);
    }

    Console.WriteLine("Returning..");
    return default(T);
}

这就是你现在所在的位置,我只添加了一些控制台日志记录代码,输出是什么?

如您所见,ExecutingWithLogging 在异步委托甚至还没有崩溃时就返回了!

让我们添加一个接受 Func<Task<T>>

的重载
private static async Task<T> ExcecuteWithLogging<T>(Func<Task<T>> function)
{
    T result;
    try
    {
        Console.WriteLine("Before calling function");
        result =  await function();
        Console.WriteLine("After calling function");

    }
    catch (Exception ex)
    {
        var message = ex.Message;
        Console.WriteLine(message);
        return default(T);

    }

    Console.WriteLine("Returning..");
    return result;
}

编译器现在会从上面提到的文章中选择这个:

The compiler prefers the method call that expects a delegate that returns a Task. That’s good, because it’s the one you’d rather use. The compiler comes to this conclusion using the type inference rules about the return value of anonymous delegates. The “inferred return type” of any async anonymous function is assumed to be a Task. Knowing that the anonymous function represented by the lambda returns a Task, the overload of Task.Run() that has the Func argument is the better match.

现在输出是:

你的异常被捕获了。那么道德是什么?再次,我引用提到的文章:

First, avoid using async lambdas as arguments to methods that expect Action and don’t provide an overload that expects a Func. If you do that, you’ll create an async void lambda. The compiler will happily assume that’s what you want.

Second, if you author methods that take delegates as arguments, consider whether programmers may wish to use an async lambda as that argument. If so, create an overload that uses Func as the argument in addition to Action. As a corollary, create an overload that takes Func> in addition to Task for arguments that return a value.

编辑 正如@Servy 在评论中指出的那样,如果您只维护 ExecuteWithLogging T 的原始版本,实际上是一个 Task<Aaa>... 但我能想到的没有超载等待的唯一方法是:

private static async Task<T> ExcecuteWithLogging<T>(Func<T> function)
{
    try
    {

        var result =  function();
        if (result is Task t)
        {
            return await t;
        }

        return result;
    }
    catch (Exception ex)
    {
        var message = ex.Message;
        Console.WriteLine(message);
    }

    Console.WriteLine("Returning..");
    return default(T);
}

但是恕我直言,除了丑陋之外,它总是会失败,因为对 function() 的调用是同步运行的,而当您想要等待任务已经结束或崩溃时。但这种方法最糟糕的部分是它甚至不编译,编译器抱怨:Can not implicitly convert type void to T

这是我的解决方案,它按照我的喜好工作:

var aaa = null;
await ExcecuteWithLogging(async () => 
                aaa = await Method());


 public async Task<string> ExcecuteWithLogging(Func<Task> action)
 {
        try
        {
            await action();
        }
        catch (Exception ex)
        {
            // handle exception

            return null;
        }

        return "ok";
 }

我遇到了类似的问题,这是我的解决方案。就我而言,我无法确定函数类型,所以我只是为异步函数创建了一个重载:

    public static T CallAndLogErrors<T, TLog>(Func<T> method, ILogger<TLog> logger)
    {
        try
        {
            return method();
        }
        catch (Exception e)
        {
            LogError(e, logger);
            throw;
        }
    }

    public static async Task<T> CallAndLogErrors<T, TLog>(Func<Task<T>> method, ILogger<TLog> logger)
    {
        try
        {
            return await method();
        }
        catch (Exception e)
        {
            LogError(e, logger);
            throw;
        }
    }

这使得调用具有 TTask<T>async Task<T> return 类型的方法成为可能,例如:

    var result = CallAndLogErrors(() => Method(), logger);
    var result = await CallAndLogErrors(() => Method(), logger);

并且捕获所有异常。