2018年8月19日 星期日

在 ASP.NET Core 專案內使用 Unity 相依性注入 DI 容器

在 ASP.NET Core 專案內使用 Unity 相依性注入 DI 容器

若使要在 ASP.NET Core 專案開發環境,將會使用 ASP.NET Core 內建的相依性注入容器功能用來在類別與其相依性之間達成控制權反轉 (IoC),不過,若您想要使用 Unity DI Container 相依性注入容器來進行取代 ,也就是我們想要在開發 ASP.NET Core 專案的時候,能夠使用 Unity 相依性注入容器來進行開發設計,首先,您需要安裝這個 Unity.Microsoft.DependencyInjection 套件( 因為,這個套件已經整合了 ASP.NET Core 的 Microsoft.Extensions.DependencyInjection 功能 ) 到您的 ASP.NET Core 專案內。
ASP.NET Core Unity Nuget Package Install
ASP.NET Core Unity Nuget Package Preview
接下來,我們需要打開 [Program.cs] 檔案節點,在 WebHost 之後,加入這個方法呼叫 .UseUnityServiceProvider(),如底下程式碼所示;不過,您還需要加入 [Unity.Microsoft.Depende] 這個命名空間,以便可以使用 [UseUnityServiceProvider] 這個方法。
C Sharp / C#
public class Program
{
    public static void Main(string[] args)
    {
        CreateWebHostBuilder(args).Build().Run();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseUnityServiceProvider()
            .UseStartup<Startup>();
}
現在,我們需要打開 [Startup.cs] 這個檔案,在這個 Startup 類別內,加入底下這段方法宣告。在這個方法裡面,我們將會需要進行抽象介面與具體實作類別的對應定義;在這裡,我們需要加入 Unity 命名空間的參考。
C Sharp / C#
public void ConfigureContainer(IUnityContainer container)
{
    // Could be used to register more types
    container.RegisterType<IMessage, ConsoleMessage>();
}
為了要方便我們進行測試在 ASP.NET Core 專案內,可以使用 Unity 相依性注入容器之功能,我們建立了底下測試用的抽象介面與兩個有實作這個介面的類別。
C Sharp / C#
public interface IMessage
{
    void Send(string message);
}

public class ConsoleMessage : IMessage
{
    public void Send(string message)
    {
        Console.WriteLine($"ConsoleMessage :{message}");
    }
}
public class FileMessage : IMessage
{
    public void Send(string message)
    {
        Console.WriteLine($"FileMessage :{message}");
    }
}
我們要來在 Controller 內,建立這個控制器的建構函式,所以,請打開 [HomeController.cs] 這個節點檔案,在這裡,我們宣告一個 IMessage 介面型別的欄位 _Message,並且建立該控制器的建構函式,我們將在該建構式內,加入 IMessage 型別參數,確認 ASP.NET Core 專案,可以透過 Unity DI Container,提供 HomeController 控制器的建構式注入能力,所以,我們將會在該建構式內,將 DI Container 注入進來的 IMessage 參數,設定給該控制器類別的欄位 _Message。
現在,我們可以在 HomeController 類別內來使用 IMessage 的各項功能了,所以,我們在 Index 動作方法內,執行了 _Message.Send("Vulcan Lee") 這個敘述,接下來,我們執行這個專案,看看是否有任何內容輸出到命令字元視窗內。
當我們執行該 ASP.NET Core 專案後,我們在命令提示字元視窗中 (下圖) ,看到了 ConsoleMessage 類別的輸出內容。
Unity DI Container Constructor Injection
C Sharp / C#
public class HomeController : Controller
{
    IMessage _Message;
    public HomeController(IMessage message)
    {
        _Message = message;
    }
    public IActionResult Index()
    {
        _Message.Send("Vulcan Lee");
        return View();
    }

    public IActionResult About()
    {
        ViewData["Message"] = "Your application description page.";

        return View();
    }

    public IActionResult Contact()
    {
        ViewData["Message"] = "Your contact page.";

        return View();
    }

    public IActionResult Privacy()
    {
        return View();
    }

    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
    public IActionResult Error()
    {
        return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
    }
}

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

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

2018年8月3日 星期五

客製化 SynchronizationContext 來讓不同執行緒 Thread 可以指定特定執行緒來執行委派方法

SynchronizationContext 類別.aspx)可以提供在各種同步處理模式中傳播同步處理內容的基本功能。這通常會在我們進行 GUI 類型應用程式的時候會用到,例如: Win Forms / WPF / Xamarin.Forms 這些類型的專案,會有這樣的需求那是因為,在這裡 GUI 類型的專案中,若想要設定 UI 控制項相關的屬性或者要變更其設定值,此時,您僅能夠在 UI Thread (UI 執行緒,或稱為 Main Thread 主執行緒) 內來執行這些程式碼。不過,有些時候,我們會需要透過多執行緒的程式設計方式,讓許多需要花費比較多時間的方法,在其他執行緒中來執行,不過,當在其他執行緒執行的方法內,有需要設定 UI 相關的屬性,我們就需要讓這些程式碼能夠在 UI 執行緒內來執行,這個時候,我們就可以透過 SynchronizationContext 類別 來滿足這樣需求。

了解更多關於 [使用 async 和 await 進行非同步程式設計] 的使用方式
了解更多關於 [Thread Class] 的使用方式
了解更多關於 [SynchronizationContext Class] 的使用方式


了解更多關於 [同步處理原始物件概觀] 的使用方式
由於 Win Forms / WPF / Xamarin.Forms 這些類型的專案內,已經內建這樣的機制,提供您在不同執行緒內,可以指定某些程式碼可以在 UI 執行緒內來執行;而在這篇文章中,我們將會建立一個 Console 類型的專案,並且自訂一個 SynchronizationContext 類別,提供相同的功能。
這篇文章的專案原始碼,可以從這個資料夾 CustomSynchronizationContext 中找到
我們繼承了 SynchronizationContext 類別,建立一個新的 MySynchronizationContext 類別,我們需要 override 覆寫 Send & Post 這兩個方法,前者是負責 會將同步訊息分派至同步處理內容,後者是負責 會將非同步訊息分派至同步處理內容;在這個練習中,我們僅會覆寫 Post 方法內的敘述。另外,我們也需要在這個類別內,建立一個佇列欄位 Queue messagesQueue,這裡將會記錄別的執行緒需要在這個特定執行緒下要執行的委派方法敘述,由於採用佇列資料結構,因此,將會採取先進先出的設計方式。因此,當在別的執行緒內呼叫了 Post 方法後,在這個方法內會使用敘述 messagesQueue.Enqueue(() => codeToRun(state))將要執行的委派方法儲存到佇列欄位內。
當進行多執行緒程式設計的時候,特別需要注意執行緒安全上的考量,因此,我們將會透過 lock (syncHandle) 敘述,確保受到保護的程式碼區段,在同一個時間內,僅會有一個執行緒可以來執行,這點需要特別注意。
最重要的是這個方法 RunMessagePump(),這個方法裡面是一個無窮迴圈,會來檢查佇列欄位內是否有要執行的委派方法,若有的話,則將該委派方法取出來,並且執行這個委派方法。
C Sharp / C#
class MySynchronizationContext : SynchronizationContext
{
    /// <summary>
    /// 待執行的訊息工作佇列
    /// </summary>
    private readonly Queue<Action> messagesQueue = new Queue<Action>();
    /// <summary>
    /// 用於同步處理之鎖定的物件
    /// </summary>
    private readonly object syncHandle = new object();
    /// <summary>
    /// 是否正在執行中
    /// </summary>
    private bool isRunning = true;

    public override void Send(SendOrPostCallback codeToRun, object state)
    {
        throw new NotImplementedException();
    }

    public override void Post(SendOrPostCallback codeToRun, object state)
    {
        lock (syncHandle)
        {
            // 將要處理的訊息工作,加入佇列中
            messagesQueue.Enqueue(() => codeToRun(state));
            SignalContinue();
        }
    }

    /// <summary>
    /// 進入訊息處理的無窮迴圈
    /// </summary>
    public void RunMessagePump()
    {
        while (CanContinue())
        {
            Console.Write(".");
            Action nextToRun = RetriveItem();
            if (nextToRun != null)
                nextToRun();
        }
    }

    /// <summary>
    /// 取出待處理的訊息工作項目
    /// </summary>
    /// <returns></returns>
    private Action RetriveItem()
    {
        lock (syncHandle)
        {
            while (CanContinue() && messagesQueue.Count == 0)
            {
                Monitor.Wait(syncHandle);
            }
            if (isRunning == true)
            {
                return messagesQueue.Dequeue();
            }
            else
            {
                return null;
            }
        }
    }

    /// <summary>
    /// 是否可以繼續執行
    /// </summary>
    /// <returns></returns>
    private bool CanContinue()
    {
        lock (syncHandle)
        {
            return isRunning;
        }
    }

    /// <summary>
    /// 停止 訊息處理的無窮迴圈 執行
    /// </summary>
    public void Cancel()
    {
        lock (syncHandle)
        {
            isRunning = false;
            SignalContinue();
        }
    }

    /// <summary>
    /// 解除鎖定,可以繼續執行
    /// </summary>
    private void SignalContinue()
    {
        Monitor.Pulse(syncHandle);
    }
}
現在,我們來進行這個客製化的 MySynchronizationContext 測試,我們將建立一個這個類別的物件 static MySynchronizationContext ctx = new MySynchronizationContext(); ,並且會先顯示這個程式是在哪個執行緒下運行,將著,使用 MySynchronizationContext.SetSynchronizationContext(ctx); 來設定我們自己設計的 同步處理的內容 synchronisation context。在程式碼最後,使用這個敘述 ctx.RunMessagePump(); 開始進行 訊息處理的無窮迴圈。


在進入無窮迴圈之前,我們會建立兩個執行緒,一個執行緒 將會讀取使用者輸入指定 Console.ReadKey();,若使用輸入 q 則會結束這個程式,在這裡會使用 ctx.Cancel(); 來取消無窮訊息處理迴圈,這樣,程式就會結束了。另外一個執行緒則是會讀取網路上網頁內容;不過,再進行讀取網頁內容的時候,將要使用敘述 ctx.Post(RunDownloadBlogger, null); 指示需要使用主執行緒來進行執行檔案下載的工作。
C Sharp / C#
class Program
{
    static MySynchronizationContext ctx = new MySynchronizationContext();
    static void Main(string[] args)
    {
        Console.Out.WriteLine("Main Thread No {0}", Thread.CurrentThread.ManagedThreadId);

        Console.WriteLine($"設定自製 SynchronizationContext 之前的 SynchronizationContext.Current : {SynchronizationContext.Current?.ToString()}");

        // 設定我們自己設計的 同步處理的內容 synchronisation context
        MySynchronizationContext.SetSynchronizationContext(ctx);

        Console.WriteLine($"設定自製 SynchronizationContext 之後的 SynchronizationContext.Current : {SynchronizationContext.Current?.ToString()}");

        Thread workerThread = new Thread(new ThreadStart(Run));
        workerThread.Start();

        Thread againThread = new Thread(new ThreadStart(() =>
        {
            while (true)
            {
                var foo = Console.ReadKey();
                if (foo.KeyChar == 'a')
                {
                    Run();
                }
                else if (foo.KeyChar == 'q')
                {
                    ctx.Cancel();
                    break;
                };
            }
        }));
        againThread.Start();

        //  開始進行 訊息處理的無窮迴圈
        ctx.RunMessagePump();
    }

    private static void Run()
    {
        Console.Out.WriteLine("Current New Thread No {0}", Thread.CurrentThread.ManagedThreadId);

        // 將要處理的委派方法項目,送入 訊息處理的無窮迴圈 執行
        ctx.Post(RunDownloadBlogger, null);
    }

    private static void RunDownloadBlogger(object state)
    {
        DownloadBlogger();
    }

    static async void DownloadBlogger()
    {
        Console.Out.WriteLine("MainProgram 下載前 on Thread No {0}", Thread.CurrentThread.ManagedThreadId);
        var client = new System.Net.WebClient();
        var webContentHomePage = await client.DownloadStringTaskAsync("https://mylabtw.blogspot.com/");
        Console.Out.WriteLine("下載 {0} 字元", webContentHomePage.Length);
        Console.Out.WriteLine("MainProgram 下載完後 on Thread No {0} ", Thread.CurrentThread.ManagedThreadId);
    }
}

了解更多關於 [使用 async 和 await 進行非同步程式設計] 的使用方式
了解更多關於 [Thread Class] 的使用方式
了解更多關於 [SynchronizationContext 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 課程