繁体   English   中英

使用 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);
    }

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

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>返回类型调用方法成为可能,例如:

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

并捕获所有异常。

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

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

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;
}

编译器现在将从上面提到的文章中选择它:

编译器更喜欢期望返回任务的委托的方法调用。 这很好,因为它是您更愿意使用的。 编译器使用关于匿名委托的返回值的类型推断规则得出这个结论。 任何异步匿名函数的“推断返回类型”都被假定为一个任务。 知道由 lambda 表示的匿名函数返回一个 Task,带有 Func 参数的 Task.Run() 的重载是更好的匹配。

现在输出是:

在此处输入图片说明

你得到了你的例外。 那么什么是道德呢? 我再次引用提到的文章:

首先,避免使用异步 lambda 作为需要 Action 的方法的参数,并且不要提供需要 Func 的重载。 如果这样做,您将创建一个异步 void lambda。 编译器会很高兴地假设这就是你想要的。

其次,如果您编写的方法将委托作为参数,请考虑程序员是否可能希望使用异步 lambda 作为该参数。 如果是这样,创建一个重载,除了 Action 之外,还使用 ​​Func 作为参数。 作为推论,创建一个重载,除了返回值的参数的 Task 之外,还需要 Func> 。

编辑正如@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()的调用是同步运行的,并且到您想要等待任务的时候已经结束或崩溃了。 但这种方法最糟糕的部分是它甚至不编译,编译器抱怨:不能隐式将类型 void 转换为 T

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM