[英]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;
}
}
这使得使用T
、 Task<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.