簡體   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