使用Lambda异步处理异常,未捕获到异常

时间:2018-07-13 12:25:53

标签: c# asynchronous exception-handling

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

3 个答案:

答案 0 :(得分:0)

这是我喜欢的解决方案,

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

答案 1 :(得分:0)

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

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

所有异常都被捕获。

答案 2 :(得分:-1)

问题在于您没有实现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);
}

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

enter image description here

您可以看到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()的重载是更好的匹配。

现在输出为:

enter image description here

您就被抓住了。那么道德是什么?再次,我引用提到的文章:

  

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

     

第二,如果您编写将委托作为参数的方法,   考虑程序员是否希望使用异步lambda那样   论点。如果是这样,请创建一个使用Func作为   除了Action之外的参数。作为必然结果,创建一个重载   除了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