2018年8月26日 星期日

在 Task.Run 內產生例外異常 Exception,我們可以在呼叫端捕捉到該例外異常嗎?

在 Task.Run 內產生例外異常 Exception,我們可以在呼叫端捕捉到該例外異常嗎?

在這篇文章,我們將要來實驗當我們使用 TaskCompletionSource 設計一個非同步工作方法,並且在這個方法內,使用 Task.Run 方法,從執行緒集區 ThreadPool 取得一個執行緒,便開始執行相關非同步處理工作;在這個非同步工作內,我們先任執行緒休息兩秒鐘,根據傳入進來的參數,決定是要在這個執行緒內直接丟出例外異常,還是要使用 TaskCompletionSource.SetException 方法,來設定這個非同步方法是失敗的,因為有這個例外異常產生出來。

了解更多關於 [Task Class] 的使用方式

首先,我們先將 await MethodAsync("New Exception"); 敘述註解起來,await MethodAsync("TaskCompletionSource.SetException"); 敘述解除註解起,便開始執行該專案,此時該程式的執行結果為
主執行緒 ID : 1
進入 Task.Run 方法內的 執行緒 ID : 5
完成非同步工作,返回的執行緒 ID : 5
藉由SetException 產生例外異常
Press any key for continuing...
看的出來,我們在呼叫端是可以正常捕捉到這個非同步方法內的其他執行緒中所產生的例外異常,並且可以在呼叫端,依據捕捉到的例外異常做出其他相關處理動作。
現在,請將 await MethodAsync("TaskCompletionSource.SetException"); 敘述註解起來,await MethodAsync("New Exception"); 敘述解除註解起,並且開始執行該專案。
現在,我們看到在呼叫這個非同步方法的呼叫端,竟然無法捕捉到非同步工作方法內的例外異常,這是因為,我們使用了 Task.Run 從 ThreadPool 取出一個執行緒來執行,如同我們知道了,若在某一執行緒中,因為設計不良進而導致了例外異常發生,此時,該應用程式將會崩潰掉,這也就是當您直接使用執行緒來設計非同步處理需求的時候,所會面臨到的困境,您需要提升您的程式設計能力與經驗,方能夠避免掉這樣的問題;原則上,我們可以在 Task.Run 內的委派方法內,在最外層就使用 Try ... Catch 將整個非同步程式碼都捕捉起來,若發生了例外異常的話,就使用 TaskCompletionSource.SetException 方法來呼叫即可。
Task.Run Thread Exception


這篇文章的範例程式碼,可以從 https://github.com/vulcanlee/CSharpNotes2018/tree/master/TaskRunException 取得
C Sharp / C#
class Program
{
    static async Task Main(string[] args)
    {
        GenerateEmptyThreads();
        try
        {
            Console.WriteLine($"主執行緒 ID : {Thread.CurrentThread.ManagedThreadId} ");
            //await MethodAsync("New Exception");
            await MethodAsync("TaskCompletionSource.SetException");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"完成非同步工作,返回的執行緒 ID : {Thread.CurrentThread.ManagedThreadId} ");
            Console.WriteLine(ex.Message);
        }

        Console.WriteLine("Press any key for continuing...");
        Console.ReadKey();
    }

    private static void GenerateEmptyThreads()
    {
        ThreadPool.QueueUserWorkItem((x) =>
        {
            Thread.Sleep(200);
        });
        ThreadPool.QueueUserWorkItem((x) =>
        {
            Thread.Sleep(1200);
        });
    }

    static Task MethodAsync(string generateExceptionAction)
    {
        TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();

        Task.Run(() =>
        {
            Console.WriteLine($"進入 Task.Run 方法內的 執行緒 ID : {Thread.CurrentThread.ManagedThreadId} ");

            Thread.Sleep(1000);
            if (generateExceptionAction == "New Exception")
                throw new Exception("直接拋出 例外異常");
            if (generateExceptionAction == "TaskCompletionSource.SetException")
                tcs.SetException(new Exception("藉由SetException 產生例外異常"));
        });
        return tcs.Task;
    }
}


了解更多關於 [Task Class] 的使用方式



關於 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 課程


沒有留言:

張貼留言