2018年8月11日 星期六

在 async void 方法內,無法捕捉到例外異常 Exception 的情況與解決方法

在 async void 方法內,無法捕捉到例外異常 Exception 的情況與解決方法

在這篇文章,我們將會透過 ILSpy 來進行 .NET 組件反組譯碼,來查看當我們使用了 async void 這樣的非同步方式設計,並且產生了一個例外異常,在我們呼叫這個非同步方法的時候,是無法捕捉到例外異常 Exception 的範例程式碼。
為了要能夠查看我們寫的範例專案的反組譯 IL 碼,我們可以安裝 ICSharpCode.Decompiler 這個 NuGet 套件,這樣,我們就可以在 Visual Studio 2017 內,直接查看到建置完成後的組件 IL 碼。所以,請在您建立的專案中,在 NuGet 安裝視窗中,輸入 ICSharpCode.Decompiler 關鍵字,找出這個套件,我們在這裡將會安裝 搶鮮版 的 4.0.0.4285-beta1 這個版本套件。
Install ICSharpCode.Decompiler NuGet Package
現在,我們完成我們的測試程式碼,我們在主程式將會同步呼叫 AsyncVoidException_Capture() 方法,在這個方法內,我們會呼叫 ThrowExcpetionAsync() 方法,不過,這個方法我們將會使用 async void 來宣告他是個非同步方法。接著,我們使用 try...catch 將會捕捉這個函數 ThrowExcpetionAsync() 會發生的任何例外異常。當您完成這個範例程式碼之後,請在 throw 這個敘述上設定一個中斷點,我們來看看 AsyncVoidException_Capture() 這個方法,是否可以捕捉到 ThrowExcpetionAsync() 方法內所產生的例外異常。
C Sharp / C#
class Program
{
    static void Main(string[] args)
    {
        AsyncVoidException_Capture();
    }

    private static void AsyncVoidException_Capture()
    {
        try
        {
            ThrowExcpetionAsync();
        }
        catch (Exception)
        {
            // 這裡無法捕捉到例外異常
            throw;
        }
    }

    private static async void ThrowExcpetionAsync()
    {
        throw new Exception("Async Void Exception");
    }
}
底下是我們實際執行結果,您會看到,在第 19 行,我們是無法捕捉到這個方法所產生的例外異常,因為,我們所設定的中斷點的敘述並沒有執行到,例外異常直接停在 async void ThrowExcpetionAsync() 方法內。
async void 方法內產生例外異常 Exception
請停止程式執行,使用滑鼠右擊專案節點,選擇 [Open output in ILSpy] 選項。
在 Visual Studio 內,使用 ILSpy 檢查 IL 碼
此時,ILSpy 程式將會啟動執行起來,請先確認左上方紅色方框處,是否勾選 [C#] 選項,並且在左方點選 [Program] 節點,現在,我們就會看到在這個建置完成後的組件內,會有許多編譯器幫我們自動產生的程式碼,也就是右下方紅色方框標示處,從這裡,我們就可以知道為什麼當 async void ThrowExcpetionAsync() 方法發生了例外異常,而 AsyncVoidException_Capture() 方法卻無法捕捉到這個例外異常。
Async Void Method
讓我們在 ILSpy 中,點選左上方下拉選單,切換成為 IL with C# 選項,接著,點選左方清單的 ThrowExcpetionAsync 項目,我們就會看到這個方法的 IL 碼和 C# 程式碼。我們看到左方清單項目中,d__2 類別出現,這個類別將會是由編譯器自動產生的一個類別,這個類別就是一個有限狀態機,他會把這個 ThrowExcpetionAsync 方法內的敘述,使用有限狀態機包裝起來,讓我們可以順利進行非同步的程式碼呼叫。
ILSpy IL with C#
請展開左方類別 d__2 節點,將會看到 MoveNext():void 這個節點,請點選這個節點,您將會看到這個方法 ThrowExcpetionAsync 內的 相關敘述,右方視窗紅色方框標示處,就是這個方法的 throw new Exception("Async Void Exception"); 之 IL 碼。
ILSpy IL with C#
現在,我們將原先的 private static async void ThrowExcpetionAsync() 函式簽章,修改成為同步方法的 private static void ThrowExcpetionAsync() 函式簽章,也就是我們將 async 這個關鍵字移除了。現在,我們可以重新建置這個專案
C Sharp / C#
class Program
{
    static void Main(string[] args)
    {
        AsyncVoidException_Capture();
    }

    private static void AsyncVoidException_Capture()
    {
        try
        {
            ThrowExcpetionAsync();
        }
        catch (Exception)
        {
            // 這裡無法捕捉到例外異常
            throw;
        }
    }

    private static void ThrowExcpetionAsync()
    {
        throw new Exception("Async Void Exception");
    }
}
好的,讓我們來執行這個同步方法,現在,我們可以看到,在 AsyncVoidException_Capture() 內,確實可以捕捉到 ThrowExcpetionAsync() 所產生的例外異常了。請接著停止執行該專案。
Async Void Method
讓我們回到 ILSpy ,點選功能表 [File] > [Reload] 選項,並且左上方下拉選單,請選擇 [C#] 選項,最後,點選右方的 [Program] 節點,現在,我們可以看到在組件中所產生這樣的同步方法,並沒有被編譯器做額外的內容。
Async Void Method
讓我們在 ILSpy 左上方的下拉選單,切換選擇 [IL with C#],我們可以查看 ThrowExcpetionAsync() 的 IL 碼,也是沒有任何編譯器自動產生的類別與有限狀態機的設定程式碼,這裡將會是很單純的只有丟出一個例外異常而已。
Async Void Method

關於 Xamarin 在台灣的學習技術資源

Xamarin 實驗室 粉絲團
歡迎加入 Xamarin 實驗室 粉絲團,在這裡,將會經常性的貼出各種關於 Xamarin / Visual Studio / .NET 的相關消息、文章、技術開發等文件,讓您可以隨時掌握第一手的 Xamarin 方面消息。
Xamarin.Forms @ Taiwan
歡迎加入 Xamarin.Forms @ Taiwan,這是台灣的 Xamarin User Group,若您有任何關於 Xamarin / Visual Studio / .NET 上的問題,都可以在這裡來與各方高手來進行討論、交流。
Xamarin 實驗室 部落格
Xamarin 實驗室 部落格 是作者本身的部落格,這個部落格將會專注於 Xamarin 之跨平台 (Android / iOS / UWP) 方面的各類開技術探討、研究與分享的文章,最重要的是,它是全繁體中文。
Xamarin.Forms 系列課程
Xamarin.Forms 系列課程 想要快速進入到 Xamarin.Forms 的開發領域,學會各種 Xamarin.Forms 跨平台開發技術,例如:MVVM、Prism、Data Binding、各種 頁面 Page / 版面配置 Layout / 控制項 Control 的用法等等,千萬不要錯過這些 Xamarin.Forms 課程

沒有留言:

張貼留言