2020年9月26日 星期六

在執行緒內拋出例外異常,可否捕捉到

在執行緒內拋出例外異常,可否捕捉到

許多人在使用 C# 非同步程式設計的 TAP 模式時候,往往會聽到這樣的強調內容:當設計 非同步方法,也就是在方法前面加上 async 修飾詞,而該方法的回傳值需要為 Task 或者 Task<T>,千萬不要把這個非同步方法的回傳值宣告為 void,因為,這樣的宣告方式,僅適用於有事件訂閱的非同步委派方法內使用。

可是,往往大家在設計非同步方法的時候,都僅會在非同步方法前面加入 async 修飾詞,而將該非同步方法的回傳值宣告為 void;就是因為回傳值為 void,因此,當要呼叫這個非同步方法的時候,就僅能夠直接呼叫該非同步方法,而不能使用 await 關鍵字 (這是因為該非同步方法的回傳值必須是一個 Task 型別的物件,因為,await 之後,要接著一個 Task 物件,這樣才能等待)。

接著,更可怕的事情就發生了,因為這樣的呼叫方式,造成了射後不理的情況,也因為如此,也就無法使用 try...catch 敘述來捕捉這樣的非同步方法的例外異常。為了要讓大家更清楚問題在哪裡,因此,底下的程式碼將會使用執行緒來說明。

首先,在主執行緒內,故意拋出例外異常 Exception,並且使用 try ... catch 敘述來捕捉這個例外異常,這樣的寫法是標準作法,理所當然會捕捉到例外異常,而不會造成處理程序崩潰;不過,接下來使用 try...catch 將建立一個執行緒,等候三秒鐘之後,拋出例外異常,最後啟動該執行緒這樣的過程都包起來,因此,在程式執行後的三秒鐘後,並無法成功攔截捕捉到例外異常,而是造成 Process 崩潰,也就是說,在該執行緒的外面,是無法使用 try...catch 敘述來捕捉到該執行緒內所產生的任何例外異常錯誤。

static void Main(string[] args)
{
    Console.WriteLine($"Main Thread Id :" +
       $"{Thread.CurrentThread.ManagedThreadId}");
    try
    {
        throw new Exception("Capture Main Exception");
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
 
    try
    {
        Thread thread = new Thread(() =>
        {
            Console.WriteLine($"New Thread Id :" +
                $"{Thread.CurrentThread.ManagedThreadId}");
            Thread.Sleep(3000);
            throw new Exception("Oh Oh");
        });
        thread.Start();
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
 
 
}


 

2020年9月25日 星期五

在 .NET C# 環境中,要存取靜態變數或者區域變數,是否都是同樣的存取速度

 

在 .NET C# 環境中,要存取靜態變數或者區域變數,是否都是同樣的存取速度

當在進行多執行緒程式設計的時候,往往會需要能夠讓多執行緒可以同時來存取一個共用的資源,也許會利用這個共用資源做為要回傳執行緒執行結果之用;可是,你知道當 .NET C# 程式在進行靜態變數或者區域變數(也就是該變數通常位於某個函式內所宣告的變數)的時候,存取區域變數的執行速度,會優於靜態全域變數,而到底差了多少?

為了要了解這個問題,特別撰寫底下的程式碼,連續存取靜態變數與區域變數數次,這裡將會執行 int.MaxValue 次,看看執行結果,不過,這裡的執行結果將會是在 Release 模式下所測試出來的結果。

Access Static : 3660ms
Access Static : 3593ms
Access Static : 3625ms
Access Local : 1705ms
Access Local : 1653ms
Access Local : 1692ms

可以看到上述的執行及果,對於存取靜態變數的時候,在這個範例將需要花費 359ˇˇ~3660 ms 之間,不過,對於存取區域變數的時候,可以看的出來,存取速度明顯快了許多,大約為 1692 ~1705 ms 之間。

因此,若要在大量迴圈到要存取變數的時候,建議不要使用靜態變數,若有需要,可以將靜態變數設定為區域變數,以便增快執行速度。

class Program
{
    static int staticInt = 0;
    static void Main(string[] args)
    {
        int localInt = 0;
        StaticObjectAccess();
        StaticObjectAccess();
        StaticObjectAccess();
        LocalObjectAccess(localInt);
        LocalObjectAccess(localInt);
        LocalObjectAccess(localInt);
    }
 
    private static void LocalObjectAccess(int localInt)
    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        for (int i = 1; i < int.MaxValue; i++)
        {
            if (i % 2 == 0)
                localInt++;
            else
                localInt--;
        }
        stopwatch.Stop();
        Console.WriteLine($"Access Local : {stopwatch.ElapsedMilliseconds}ms");
        return;
    }
 
    private static void StaticObjectAccess()
    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        for (int i = 1; i < int.MaxValue; i++)
        {
            if (i % 2 == 0)
                staticInt++;
            else
                staticInt--;
        }
        stopwatch.Stop();
        Console.WriteLine($"Access Static : {stopwatch.ElapsedMilliseconds}ms");
        return;
    }
}





2020年9月21日 星期一

建立與查看Contoso 大學資料庫的 ERD Entity-Relationship Diagram

 

建立與查看Contoso 大學資料庫的 ERD Entity-Relationship Diagram

在上一篇文章 Entity Framework Core - 建立練習使用的 Contoso University 資料庫 中,已經說明如何在開發電腦環境中,建立一個測試用的資料庫,並且該資料庫內也產生了許多測試紀錄。可是,在這個資料庫內,這些資料表 Table 之間的關聯為何? 也就是在進行關聯式資料庫設計時候,經常會用到的 設計時候,ERD Entity-Relationship Diagram 是呈現甚麼內容? 在這篇文章中,將會說明如何得到這方面的資訊。

安裝 SQL Server Management Studio (SSMS)

使用 SQL Server Management Studio (SSMS) 產生 ERD

  • 安裝好 SQL Server Management Studio (SSMS) 之後,請打開這個應用程式

  • 首先會看到 [連線至伺服器] 對話窗

  • 請在 [伺服器名稱] 內,輸入 (localdb)\.

  • 最後,點選 [連線] 按鈕

  • 成功連線之後,將會顯示 [物件總管] 視窗

  • 請展開 [物件總管] 視窗內的 [(localdb.)] > [資料庫] > [School] 節點

  • 滑鼠右擊 [資料庫圖表] 節點,從彈出功能表選取 [新增資料庫圖表] 選項

  • 第一次將會出現 [此資料庫沒有使用資料庫圖表所需的一或多個支援物件。您要建立它們嗎?] 訊息

  • 點選 [是] 按鈕

  • 此時將會出現 [加入資料表] 對話窗

  • 請將全部資料表都選取起來

    想要全部選取,可以先點選第一個資料表 (Course),接著按下 [Shift] 按鍵,點選最後一個資料表(StudentGrade)

  • 最後,點選 [加入] 按鈕

  • 若這些資料表沒有正常排列顯示,請在空白處,使用滑鼠右擊,選擇 [排列資料表],這樣就會看到這個資料庫所以資料表之間的關聯 ERD,哪些是 一對一關係、一對多關係、多對一關係、多對多關係









Entity Framework Core - 建立練習使用的 Contoso University 資料庫

 

Entity Framework Core - 建立練習使用的 Contoso University 資料庫

今年將會撰寫許多關於 Entity Framework Core 的設計與使用說明文章,不過,當想要實際進行演練這些 Entity Framework Core 的用法,還是需要有個範例資料庫,並且該資料庫內最好已經具備了許多測試資料;在這裡,將會準備 Contoso University 資料庫 這個範例資料庫,因此,接下來就來說明如何安裝這個練習用的資料庫到讀者的開發電腦上。

使用 VS2019 匯入資料庫

  • 開啟 [Visual Studio 2019]

  • 選擇對話窗右下方的 [不使用程式碼繼續(W)] 連結

  • 點選 Visual Studio 2019 功能表的 [檢視] > [SQL Server 物件總管] 選項

    SQL Server 物件總管

  • 在 [SQL Server 物件總管] 視窗內,展開 [SQL Server] 節點,便會 [(localdb)\MSSQLLocalDB] 這個資料庫服務

  • 滑鼠右擊剛剛看到的 localdb 節點,選擇 [新增查詢] 功能表選項

  • 此時,將會顯示一個 [SQLQuery1.sql] 視窗

  • 請使用瀏覽器打開這個連結 Contoso University 資料庫,接這些內容複製到系統的剪貼簿內

  • 切換到 [SQLQuery1.sql] 視窗內,把這些內容貼到該視窗內

  • 點選該視窗標籤下方的綠色三角形,執行這些 SQL 敘述

    這個 Contoso University 資料庫 SQL 敘述,將會先把原先的 School 資料庫刪除掉 (可以從上圖的第 9~11 行看到這些 SQL 敘述) ,接著,將會建立 School 資料庫建立起來,並且建立相關資料表與產生測試資料紀錄到資料庫內。

    若執行完成之後,可以滑鼠右擊 [SQL Server 物件總管] 內的 localdb 節點,選擇 [重新整理]

確認練習用的資料庫已經建立完成

  • 請在 [SQL Server 物件總管] 視窗內的 [(localdb)\MSSQLLocalDB] > [資料庫] 節點內,找到 [School] 資料庫節點

  • 展開這個 [School] 節點,便可以看到最新產生的練習用資料庫了

2020年5月12日 星期二

WPF Prism 4 - 使用 Prism 開發框架來製作一個具有 WPF 導航檢視功能教學說明程式

WPF Prism 4 - 使用 Prism 開發框架來製作一個具有 WPF 導航檢視功能教學說明程式

在這篇文章,將會使用 Prism 來開發出具有導航檢視功能的 WPF 應用程式。
首先,當應用程式啟動之後,將會使用 RequestNavigate 方法,切換到 MyView 這個檢視畫面上,如同下圖。
WPF Prism
當點選 [導航下頁] 按鈕之後,將會切換到 View1 這個檢視畫面上,如同下圖。
WPF Prism
在 View1 檢視內,當點選 [導航下頁] 按鈕之後,將會切換到 View2 這個檢視畫面上,如同下圖;而當點選 [導航上頁] 按鈕之後,將會切換到 MyView 頁面。
WPF Prism
這個說明專案的原始碼位於 WPFPrismViewNavigation

準備工作

  • 首先,先要安裝 [Prism Template Pack] 到 Visual Studio 2019 內
  • 打開 Prism Template Pack 擴充功能網站
  • 下載並且安裝這個擴充功能

建立 WPF for Prism 的專案

  • 打開 Visual Studio 2019
  • 點選右下方的 [建立新的專案] 按鈕
  • [建立新專案] 對話窗將會顯示在螢幕上
  • 從[建立新專案] 對話窗的中間區域,找到 [Prism Blank App (WPF)] 這個專案樣板選項,並且選擇這個項目
    若沒有看到這個選項,則表示你的 Visual Studio 2019 開發環境中,還沒有安裝 Prism Template Pack 擴充功能
  • 點選右下角的 [下一步] 按鈕
  • 現在 [設定新的專案] 對話窗將會出現
  • 請在這個對話窗內,輸入適當的 [專案名稱] 、 [位置] 、 [解決方案名稱]
    在這裡請輸入 [專案名稱] 為 WPFPrismViewNavigation
  • 在最下方的 [架構] 部分,建議選取最新的 [.NET Framework 4.8]
  • 完成後,請點選 [建立] 按鈕
  • 當出現 [PRISM PROJECT WIZARD] 對話窗的時候
  • 請在 [Select Container] 選擇容器這個欄位之下拉選單,選擇你要使用的 DI 相依性注入容器,我個人習慣使用 Unity 這個 Ioc 容器
  • 之後,點選 [CREATE PROJECT] 這個按鈕
稍微等會一段時間,具有 Prism 開發框架的 WPF 專案將會建立起來

加入 WPF 套件

  • 滑鼠右擊剛剛建立的專案節點
  • 選擇 [管理 NuGet 套件]
  • 找到 [Prism.Wpf] 這個套件,並且安裝起來

設定第一個要顯示的 View 檢視

在剛剛建立的專案中,加入三個 View 與 ViewModel

  • 滑鼠右擊剛剛建立的專案,選擇 [加入] > [新增資料夾],建立 Views & ViewModels 這兩個資料夾
  • 滑鼠右擊 [Views] 資料夾
  • 選擇 [加入] > [新增項目]
  • 此時,[新增項目] 對話窗將顯示出來
  • 請在該對話窗的左方,展開節點到 [已安裝] > [Visual C#] > [Prism] > [WPF]
  • 在中間區域選擇 [Prism UserControl (WPF)] 選項
  • 在下方名稱欄位輸入 MyView
  • 最後點選 [新增] 按鈕
此時,將會看到該專案的 [Views] 資料夾內新產生了一個 [MyView.xaml] 這個檔案,另外,在 [ViewModel] 資料夾內也產生了一個 [MyViewViewModel] 這個類別檔案。
打開 [MyView.xaml] 檔案,填入底下 XAML 標記宣告
<UserControl x:Class="WPFPrismViewNavigation.Views.MyView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:prism="http://prismlibrary.com/"             
             prism:ViewModelLocator.AutoWireViewModel="True">
    <Grid Background="LightGray">
        <StackPanel
            Orientation="Vertical">
            <TextBlock Text="MyView"
                   HorizontalAlignment="Center" VerticalAlignment="Center"
                   FontSize="48" Foreground="White"/>
            <TextBlock Text="{Binding Message}"
                       HorizontalAlignment="Center" VerticalAlignment="Center"
                       FontSize="30"
                       Foreground="Green"/>
            <Button 
                HorizontalAlignment="Center"
                Command="{Binding GoNextCommand}">導航到下頁</Button>
        </StackPanel>
    </Grid>
</UserControl>
在這個檢視內,將會宣告一個按鈕並且有個 Command 命令屬性
  • 請要修正該檢視會搭配的 ViewModel 類別,請打開 [ViewModels] 資料夾下的 MyViewViewModel.cs 檔案
  • 使用底下 C# 程式碼進行替換
public class MyViewModel : BindableBase , INavigationAware
{
    private string message;
    private readonly IRegionManager regionManager;
 
    public string Message
    {
        get { return message; }
        set { SetProperty(ref message, value); }
    }
    public DelegateCommand GoNextCommand { get; set; }
    public int Counter { get; set; }
    public MyViewModel(IRegionManager regionManager)
    {
        this.regionManager = regionManager;
        GoNextCommand = new DelegateCommand(() =>
        {
            regionManager.RequestNavigate("ContentRegion", nameof(View1));
        });
    }
 
    public bool IsNavigationTarget(NavigationContext navigationContext)
    {
        return true;
    }
 
    public void OnNavigatedFrom(NavigationContext navigationContext)
    {
    }
 
    public async void OnNavigatedTo(NavigationContext navigationContext)
    {
        await Task.Yield();
        Message = navigationContext.NavigationService.Journal.CanGoBack == false ? "尚未開始導航 "+ Counter++ : "可以回上一頁";
        //regionManager.Regions["ContentRegion"].NavigationService.Journal.Clear();
    }
}
在此 ViewModel 類別內,將會設計這個按鈕的命令觸發委派方法,因此,當使用者點選這個按鈕的時候,將會使用 regionManager.RequestNavigate("ContentRegion", nameof(View1)); 敘述,切換到 View1 檢視內。
另外,這個 ViewModel 也有實作出 INavigationAware 這個介面,因此,將會需要實作出三個方法,分別會於頁面切換過程中,會被觸發執行的。
接下來,繼續產生一個 View1 檢視
  • 首先,滑鼠右擊 [Views] 資料夾
  • 選擇 [加入] > [新增項目]
  • 此時,[新增項目] 對話窗將顯示出來
  • 請在該對話窗的左方,展開節點到 [已安裝] > [Visual C#] > [Prism] > [WPF]
  • 在中間區域選擇 [Prism UserControl (WPF)] 選項
  • 在下方名稱欄位輸入 View1
  • 最後點選 [新增] 按鈕
此時,將會看到該專案的 [Views] 資料夾內新產生了一個 [View1.xaml] 這個檔案,另外,在 [ViewModel] 資料夾內也產生了一個 [View1ViewModel] 這個類別檔案。
打開 [View1.xaml] 檔案,填入底下 XAML 標記宣告
<UserControl x:Class="WPFPrismViewNavigation.Views.View1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:prism="http://prismlibrary.com/"             
             prism:ViewModelLocator.AutoWireViewModel="True">
    <Grid Background="LightBlue">
        <StackPanel
            Orientation="Vertical">
            <TextBlock Text="View1"
                   HorizontalAlignment="Center" VerticalAlignment="Center"
                   FontSize="48" Foreground="White"/>
            <TextBlock Text="{Binding Message}"
                       HorizontalAlignment="Center" VerticalAlignment="Center"
                       FontSize="30"
                       Foreground="Green"/>
            <StackPanel
                Orientation="Horizontal"
                HorizontalAlignment="Center">
                <Button 
                    HorizontalAlignment="Center"
                    Command="{Binding GoPrevCommand}">導航到上頁</Button>
                <Button 
                    HorizontalAlignment="Center"
                    Command="{Binding GoNextCommand}">導航到下頁</Button>
            </StackPanel>
        </StackPanel>
    </Grid>
</UserControl>
在這個檢視內,將會宣告一個按鈕並且有個 Command 命令屬性
  • 請要修正該檢視會搭配的 ViewModel 類別,請打開 [ViewModels] 資料夾下的 View1ViewModel.cs 檔案
  • 使用底下 C# 程式碼進行替換
public class View1ViewModel : BindableBase, INavigationAware
{
    private string message;
    private readonly IRegionManager regionManager;
    private readonly IRegionNavigationService regionNavigationService;
 
    public string Message
    {
        get { return message; }
        set { SetProperty(ref message, value); }
    }
    public int Counter { get; set; }
    public DelegateCommand GoNextCommand { get; set; }
    public DelegateCommand GoPrevCommand { get; set; }
    public View1ViewModel(IRegionManager regionManager, IRegionNavigationService regionNavigationService)
    {
        this.regionManager = regionManager;
        this.regionNavigationService = regionNavigationService;
        GoNextCommand = new DelegateCommand(() =>
        {
            regionManager.RequestNavigate("ContentRegion", nameof(View2));
        });
        GoPrevCommand = new DelegateCommand(() =>
        {
            regionManager.Regions["ContentRegion"].NavigationService.Journal.GoBack();
        });
    }
 
    public bool IsNavigationTarget(NavigationContext navigationContext)
    {
        return true;
    }
 
    public void OnNavigatedFrom(NavigationContext navigationContext)
    {
    }
 
    public async void OnNavigatedTo(NavigationContext navigationContext)
    {
        await Task.Yield();
        Message = navigationContext.NavigationService.Journal.CanGoBack == false ? "尚未開始導航 ": "可以回上一頁 " + Counter++;
    }
}
在此 ViewModel 類別內,將會設計兩個按鈕的命令觸發委派方法,因此,當使用者點選 [GoNextCommand] 這個按鈕的時候,將會使用 regionManager.RequestNavigate("ContentRegion", nameof(View2)); 敘述,切換到 View2 檢視內,當使用者點選 [GoPrevCommand] 這個按鈕的時候,將會使用 regionManager.Regions["ContentRegion"].NavigationService.Journal.GoBack(); 敘述,回到上一頁檢視,也就是 MyView 檢視內。
另外,這個 ViewModel 也有實作出 INavigationAware 這個介面,因此,將會需要實作出三個方法,分別會於頁面切換過程中,會被觸發執行的。
最後,產生一個 View2 檢視
  • 首先,滑鼠右擊 [Views] 資料夾
  • 選擇 [加入] > [新增項目]
  • 此時,[新增項目] 對話窗將顯示出來
  • 請在該對話窗的左方,展開節點到 [已安裝] > [Visual C#] > [Prism] > [WPF]
  • 在中間區域選擇 [Prism UserControl (WPF)] 選項
  • 在下方名稱欄位輸入 View2
  • 最後點選 [新增] 按鈕
此時,將會看到該專案的 [Views] 資料夾內新產生了一個 [View2.xaml] 這個檔案,另外,在 [ViewModel] 資料夾內也產生了一個 [View2ViewModel] 這個類別檔案。
打開 [View2.xaml] 檔案,填入底下 XAML 標記宣告
<UserControl x:Class="WPFPrismViewNavigation.Views.View2"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:prism="http://prismlibrary.com/"             
             prism:ViewModelLocator.AutoWireViewModel="True">
    <Grid Background="LightPink">
        <StackPanel
            Orientation="Vertical">
            <TextBlock Text="View2"
                   HorizontalAlignment="Center" VerticalAlignment="Center"
                   FontSize="48" Foreground="White"/>
            <TextBlock Text="{Binding Message}"
                       HorizontalAlignment="Center" VerticalAlignment="Center"
                       FontSize="30"
                       Foreground="Green"/>
            <StackPanel
                Orientation="Horizontal"
                HorizontalAlignment="Center">
                <Button 
                    HorizontalAlignment="Center"
                    Command="{Binding GoPrevCommand}">導航到上頁</Button>
            </StackPanel>
        </StackPanel>
    </Grid>
</UserControl>
在這個檢視內,將會宣告一個按鈕並且有個 Command 命令屬性
  • 請要修正該檢視會搭配的 ViewModel 類別,請打開 [ViewModels] 資料夾下的 View2ViewModel.cs 檔案
  • 使用底下 C# 程式碼進行替換
public class View2ViewModel : BindableBase, INavigationAware
{
    private string message;
    private readonly IRegionManager regionManager;
 
    public string Message
    {
        get { return message; }
        set { SetProperty(ref message, value); }
    }
    public int Counter { get; set; }
    public DelegateCommand GoPrevCommand { get; set; }
    public View2ViewModel(IRegionManager regionManager)
    {
        this.regionManager = regionManager;
        GoPrevCommand = new DelegateCommand(() =>
        {
            regionManager.Regions["ContentRegion"].NavigationService.Journal.GoBack();
        });
    }
 
    public bool IsNavigationTarget(NavigationContext navigationContext)
    {
        return true;
    }
 
    public void OnNavigatedFrom(NavigationContext navigationContext)
    {
    }
 
    public async void OnNavigatedTo(NavigationContext navigationContext)
    {
        await Task.Yield();
        Message = navigationContext.NavigationService.Journal.CanGoBack == false ? "尚未開始導航 " + this.GetHashCode() : "可以回上一頁 " + Counter++;
    }
}
在此 ViewModel 類別內,將會設計這個按鈕的命令觸發委派方法,因此,當使用者點選這個按鈕的時候,將會使用 regionManager.Regions["ContentRegion"].NavigationService.Journal.GoBack(); 敘述,退回到 View1 檢視內。
另外,這個 ViewModel 也有實作出 INavigationAware 這個介面,因此,將會需要實作出三個方法,分別會於頁面切換過程中,會被觸發執行的。

修正主專案

  • 在該專案根目錄下,找到 App.xaml 這個檔案節點
  • 展開該節點,將會看到一個 [App.xaml.cs] 這個節點
  • 滑鼠雙擊打開 [App.xaml.cs] 這個節點
  • 建立一個覆寫 ConfigureModuleCatalog 方法
  • 將該方法修改成為底下的程式碼
public partial class App
{
    protected override Window CreateShell()
    {
        return Container.Resolve<MainWindow>();
    }
 
    protected override void RegisterTypes(IContainerRegistry containerRegistry)
    {
        containerRegistry.RegisterForNavigation<MyView>();
        containerRegistry.RegisterForNavigation<View1>();
        containerRegistry.RegisterForNavigation<View2>();
    }
 
    // 在這裡指定 Region 要顯示的 View ,也是可行的
    public override void Initialize()
    {
        base.Initialize();
        IContainerProvider container = Container;
        IRegionManager regionManager = container.Resolve<IRegionManager>();
        regionManager.RequestNavigate("ContentRegion", nameof(MyView));
    }
}

執行與測試

現在可以執行這個使用 Prism 開發的 WPF 專案,看看是不是如同第一篇文章規劃的一樣方式來進行運作。