[英]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.