2018年8月25日 星期六

在 .NET 使用 執行緒的同步 Synchronization - ManualResetEvent vs AutoResetEvent

在 .NET 使用 執行緒的同步 Synchronization - ManualResetEvent vs AutoResetEvent

當您在開發多執行應用程式的時候,將會遇到這樣的需求,要能夠在不同的執行緒執行過程中,進行互相的協調、以便同步進行後續的處理工作。在這裡,我們將模擬一個實際的情境,例如,在賽馬場中,將會有五組參賽人員要進行比賽,而要比賽之前,大家需要依序進入到起跑點內,這樣,裁判才能夠鳴槍,讓大家一同起跑;另外,我們還需要能夠下達一個指定,讓這個程式可以結束執行。
這篇文章的範例程式碼,可以從 https://github.com/vulcanlee/CSharpNotes2018/tree/master/ManualResetEventDemo 取得
面對這樣的需求,我們將會需要用到7個執行緒(其中,有6個背景執行緒,1個前景執行緒)來完成這樣的情境
  • 每個參賽人員的執行緒
    因為會有五組人員要參加比賽,這裡共會產生五個執行緒,在此,使用 ThreadPool.QueueUserWorkItem 產生5個執行緒,並且可以看出,這五個執行緒都是背景執行緒,也就是說,若主執行緒執行完成之後,不論這五個執行緒是否有執行完成,該處理程序也會結束執行的。
    這個執行緒的委派方法會模擬要進行準備工作,因此,當參賽人員之比賽執行緒開始執行的時候,會模擬等候 2~5 秒鐘的時間。
    接著,會使用 WaitForGameStart.WaitOne(); 方法,等候裁判通知比賽要開始 (在裁判端的執行緒中,會透過 WaitForGameStart.Set(); 方法,通知這五個執行緒比賽正式開始);當執行緒執行WaitForGameStart.WaitOne(); 方法 的時候,該執行將會在 封鎖 (Block) 狀態下,也就是,這個執行緒無法執行任何程式碼。
    然後,在比賽執行緒將會模擬進行比賽,在這裡比賽的執行緒中,將會模擬休息 5~10 秒鐘,最後,比賽用的執行緒將會結束執行。
  • 裁判的執行緒
    我們將會透過 ThreadPool.QueueUserWorkItem 產生一個背景執行緒,作為裁判下達通知與進行相關動作的執行程式,在這個執行緒中,會使用 ConsoleKeyInfo key = Console.ReadKey(); 等候裁判下達指示,若輸入 B 按鍵,便會下達 WaitForGameStart.Set(); 方法,讓參賽的五個執行緒開始同時來進行比賽;若下達 Q 按鍵,則會執行 WaitForExitProgram.Set(); 方法,此時,在主執行緒中的最後一行敘述,將不會被封鎖 Block 住,而會繼續執行;因為主執行緒為前景執行緒,當前景執行緒執行完畢後,不論背景執行緒是否有執行完成,整個處理程序將會結束運行。
這個螢幕截圖,將會是該範例程式的執行結果。
ManualResetEvent AutoResetEvent
底下是上述說明的測試程式碼,若想要了解 AutoResetEvent 與 ManualResetEvent 這兩個類別的差異,可以試著將 WaitForGameStart 變數的型別修改成為 AutoResetEvent,體驗一下執行結果有何差異。
不論是 AutoResetEvent 與 ManualResetEvent 都是提供執行緒間的同步處理工作,該物件內都有初始狀態設定,在我們這個範例中,設定為 fasle,表示尚未收到其他執行緒的為已收到訊號通知,因此,當該執行緒執行到 WaitForGameStart.WaitOne(); 敘述的值後,該執行緒將會被凍結,封鎖 Block 在行程式碼上。
此時,您可以想像有個閘門,只要您下令將閘門打開 (在其他執行緒上執行 WaitForGameStart.Set(); 方法),這些被封鎖的執行緒將會自動執行;而 AutoResetEvent 與 ManualResetEvent 的差異在於,對於前者若在其他執行緒上執行了 WaitForGameStart.Set(); 敘述,該閘門會開啟,但是,只允許一個人通過,並且就會立即關閉起來;而後者,則是打開之後,所有的執行緒都會繼續執行下去,除非我們下達關閉閘門指令。
您可以試著修改 static ManualResetEvent WaitForGameStart = new ManualResetEvent(false); 為 static AutoResetEvent WaitForGameStart = new AutoResetEvent(false),接著執行看看差異在哪裡。
C Sharp / C#
class Program
{
    // 等候通知便結束程式執行
    static AutoResetEvent WaitForExitProgram = new AutoResetEvent(false);
    // 等候通知,便開始進行比賽
    static ManualResetEvent WaitForGameStart = new ManualResetEvent(false);

    // 定義隨機亂數用於測試之用
    static Random 隨機亂數 = new Random();

    static void Main()
    {
        // 使用 ThreadPool.QueueUserWorkItem 產生5個執行緒
        Console.WriteLine("開始比賽前的比賽準備");
        for (int i = 1; i <= 5; i++)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(進行比賽), i);
        }
        Console.WriteLine("等候裁判通知,比賽就會開始");


        ThreadPool.QueueUserWorkItem((x) =>
        {
            while (true)
            {
                ConsoleKeyInfo key = Console.ReadKey();
                if (key.Key == ConsoleKey.B)
                {
                    WaitForGameStart.Set();
                }
                if (key.Key == ConsoleKey.Q)
                {
                    WaitForExitProgram.Set();
                }
            }
        });

        Console.WriteLine("等候通知,該程式就會結束執行");
        WaitForExitProgram.WaitOne();
    }

    static void 進行比賽(Object state)
    {
        // 向等候的執行緒通知發生事件
        int are = (int)state;
        int time = 1000 * 隨機亂數.Next(2, 5);
        Console.WriteLine($"參賽者 {are} 需要 {time} 毫秒的時間來準備");
        Thread.Sleep(time);
        Console.WriteLine($"參賽者 {are} 準備好了");
        // 設定作業已經成功
        WaitForGameStart.WaitOne();
        Console.WriteLine($"參賽者 {are} 開始進行比賽");
        time = 1000 * 隨機亂數.Next(5, 10);
        Thread.Sleep(time);
        Console.WriteLine($"參賽者 {are} 抵達終點");
    }
}

關於 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月22日 星期三

在 ASP.NET 使用 執行緒的同步內容 SynchronizationContext 進行非同步工作處理問題 HttpContext

在 ASP.NET 使用 執行緒的同步內容 SynchronizationContext 進行非同步工作處理問題 HttpContext

SynchronizationContext類別是一個基底類別,它提供執行緒內容的同步處理需求,它可以幫助我們在在不同的同步情況下,進行進行非同步/同步操作正確行為。若想要更深入去了解 SynchronizationContext 這個類別功能,可以參考 不可或缺的 SynchronizationContext 文章。
在這篇文章中,我們將要來說明在 ASP.NET 使用 執行緒的同步內容 SynchronizationContext 進行非同步工作處理問題,也就是我們在 ASP.NET 專案程式碼開發時候,當使用者發出一個 Http Request 請求的時候, IIS 將會從執行緒集區取出一個執行緒,用來處理這個使用者請求;此時,在這個執行緒中,當時的同步內容 SynchronizationContext 將會是一個 System.Web.AspNetSynchronizationContext 類別物件,這個類別物件將會用於處理 ASP.NET 的執行緒同步內容的處理工作。因此,若我們在這個 Http Request 請求執行緒中,另外進行一個非同步的工作,而該非同步的工作將會在另外一個新的執行緒中來執行,此時,在這個新的執行緒中,將無法取得 System.Web.HttpContext.Current 屬性值,此時,該屬性值將會是 null。
另外,當非同步工作完成之後,若沒有指定返回的執行緒要進行同步內容切換,則會造成呼叫非同步工作後的相關程式碼,也無法取得 System.Web.HttpContext.Current 屬性值,因此,若沒有遵守這樣的要求,將會造成您的應用程式發生例外異常的問題。這樣的問題將會發生在您的 ASP.NET 專案中有使用到 多執行緒 Multiple Thread 或者 非同步 Asynchronous 處理需求,若沒有特別注意,將會發生不可預期的例外異常問題。
我們來展示當您進行多執行緒或者非同步工作程式設計並且沒有遵循這樣要求,會發生甚麼樣的問題,以及要如何解決此一問題。在這裡您需要知道 ASP.NET 有繼承 SynchronizationContext 類別,實作出 System.Web.AspNetSynchronizationContext 類別,當您在 Http Request 請求執行緒下執行程式碼,此時,需要存取 System.Web.HttpContext.Current 屬性值,您可以透過 AspNetSynchronizationContext 物件幫助您做到同步內容,如此,也就是不會發生不明例外異常。
底下是我們要測試的 HomeController 程式碼。
首先,我們來看看當 continueOnCapturedContext = true 的執行結果
在這個測試中, Index 動作方法將會等候 Task.Run 這個非同步執行完成,而且,在這個非同步工作的等候前後,我們都可以看到當時的 Http Context 是都有值的,他的型別為 System.Web.AspNetSynchronizationContext。不過,在 Tas.Run 內的委派方法,在進行 Thread.Sleep 方法呼叫前後,我們是都無法捕捉到任何關於 System.Web.HttpContext.Current 的屬性值,這是因為要等候 Task.Run 這個非同步工作的時候,該非同步工作裡面會產生一個新的執行緒 (此執行緒 ID 為 9),而對於當使用者請求這個 Http Request 請求時候所用的執行緒 (此執行緒 ID 為 8) 是不同的,因此,在新的執行緒中,是無法讀取到任何 System.Web.HttpContext.Current 的屬性值。
不過,由於我們有在 Task.Run 這個非同步方法之後,又執行了 ConfigureAwait(true) 這個方法,所以,當非同步工作執行完成之後,將會透過執行緒同步內容,也就是 System.Web.AspNetSynchronizationContext 類別的物件,將原先 Http Request 請求的相關內容,複製到這個從非同步工作返回的執行緒中,故,我們可以還是可以看到與存取 System.Web.HttpContext.Current 的屬性值。在這裡您會看到,一開始使用者 Http 請求的執行緒為 ID=8,非同步工作的執行緒 ID=9,不過,這裡不像是 WPF 類型的應用程式,在 ASP.NET 專案程式中,在完成非同步工作之後,將會從執行緒集區 ThreadPool 內,取得一個新的執行緒,透過同步內容物件的幫助,還原 System.Web.HttpContext.Current ,因此,我們看到當完成非同步工作的執行緒,是與當初請求的執行緒不同,但是,還是可以繼續存取 System.Web.HttpContext.Current 的屬性值。
進行 System.Web.HttpContext.Current 多執行緒的同步內文測試
執行緒 Thread → 8
工作排程 Task scheduler → System.Threading.Tasks.ThreadPoolTaskScheduler
同步內文 SynchronizationContext → System.Web.AspNetSynchronizationContext
Http內文 Http Context → System.Web.HttpContext


等候 非同步工作 完成
執行緒 Thread → 8
工作排程 Task scheduler → System.Threading.Tasks.ThreadPoolTaskScheduler
同步內文 SynchronizationContext → System.Web.AspNetSynchronizationContext
Http內文 Http Context → System.Web.HttpContext


正在非同步工作中 / Sleep 方法將會被呼叫
執行緒 Thread → 9
工作排程 Task scheduler → System.Threading.Tasks.ThreadPoolTaskScheduler
同步內文 SynchronizationContext →
Http內文 Http Context →

準備結束非同步工作 / 是否要回到原先執行緒的同步內文中 True
執行緒 Thread → 9
工作排程 Task scheduler → System.Threading.Tasks.ThreadPoolTaskScheduler
同步內文 SynchronizationContext →
Http內文 Http Context →

非同步工作 已經完成
執行緒 Thread → 9
工作排程 Task scheduler → System.Threading.Tasks.ThreadPoolTaskScheduler
同步內文 SynchronizationContext → System.Web.AspNetSynchronizationContext
Http內文 Http Context → System.Web.HttpContext
ASP.NET SynchronizationContext
現在,我們來看看當 continueOnCapturedContext = false 的執行結果
在這個測試中, Index 動作方法將會等候 Task.Run 這個非同步執行完成,而且,在這個非同步工作的等候前後,我們都可以看到當時的 Http Context 是都有值的,他的型別為 System.Web.AspNetSynchronizationContext。不過,在 Tas.Run 內的委派方法,在進行 Thread.Sleep 方法呼叫前後,我們是都無法捕捉到任何關於 System.Web.HttpContext.Current 的屬性值,這是因為要等候 Task.Run 這個非同步工作的時候,該非同步工作裡面會產生一個新的執行緒 (此執行緒 ID 為 9),而對於當使用者請求這個 Http Request 請求時候所用的執行緒 (此執行緒 ID 為 8) 是不同的,因此,在新的執行緒中,是無法讀取到任何 System.Web.HttpContext.Current 的屬性值。
不過,由於我們有在 Task.Run 這個非同步方法之後,又執行了 ConfigureAwait(false) 這個方法,因此,當非同步工作執行完成之後,將會不會執行執行緒同步內容工作,將原先 Http Request 請求的相關內容,複製到這個從非同步工作返回的執行緒中,故,我們是無法看到與存取 System.Web.HttpContext.Current 的屬性值。在這裡您會看到,一開始使用者 Http 請求的執行緒為 ID=8,非同步工作的執行緒 ID=9,不過,這裡不像是 WPF 類型的應用程式,在 ASP.NET 專案程式中,在完成非同步工作之後,將會從執行緒集區 ThreadPool 內,取得一個新的執行緒,透過同步內容物件的幫助,還原 System.Web.HttpContext.Current ,因此,我們看到當完成非同步工作的執行緒,是與當初請求的執行緒不同,但是,還是可以繼續存取 System.Web.HttpContext.Current 的屬性值。
進行 System.Web.HttpContext.Current 多執行緒的同步內文測試
執行緒 Thread → 8
工作排程 Task scheduler → System.Threading.Tasks.ThreadPoolTaskScheduler
同步內文 SynchronizationContext → System.Web.AspNetSynchronizationContext
Http內文 Http Context → System.Web.HttpContext


等候 非同步工作 完成
執行緒 Thread → 8
工作排程 Task scheduler → System.Threading.Tasks.ThreadPoolTaskScheduler
同步內文 SynchronizationContext → System.Web.AspNetSynchronizationContext
Http內文 Http Context → System.Web.HttpContext


正在非同步工作中 / Sleep 方法將會被呼叫
執行緒 Thread → 9
工作排程 Task scheduler → System.Threading.Tasks.ThreadPoolTaskScheduler
同步內文 SynchronizationContext →
Http內文 Http Context →

準備結束非同步工作 / 是否要回到原先執行緒的同步內文中 False
執行緒 Thread → 9
工作排程 Task scheduler → System.Threading.Tasks.ThreadPoolTaskScheduler
同步內文 SynchronizationContext →
Http內文 Http Context →

非同步工作 已經完成
執行緒 Thread → 9
工作排程 Task scheduler → System.Threading.Tasks.ThreadPoolTaskScheduler
同步內文 SynchronizationContext →
Http內文 Http Context →
ASP.NET SynchronizationContext
C Sharp / C#
public class HomeController : Controller
{
    public async Task<ActionResult> Index()
    {
        // 是否要回到原先執行緒的同步內文中
        // https://msdn.microsoft.com/zh-tw/library/system.threading.tasks.task.configureawait(v=vs.110).aspx
        bool continueOnCapturedContext = true;
        Response.Write($"{Log("進行 System.Web.HttpContext.Current 多執行緒的同步內文測試")}<br/><hr/><br/>");
        try
        {
            Response.Write($"{Log("等候 非同步工作 完成")}<br/><hr/><br/>");

            // 使用我們指定的條件,進行呼叫非同步方法
            await Task.Run(async () =>
            {
                Response.Write($"{Log($"正在非同步工作中 / Sleep 方法將會被呼叫")}<br/>");
                // 要休息 1000 毫秒數
                System.Threading.Thread.Sleep(1000);
                Response.Write($"{Log($"準備結束非同步工作 / 是否要回到原先執行緒的同步內文中 <strong>{continueOnCapturedContext.ToString()}</strong>")}<br/>");
            }).ConfigureAwait(continueOnCapturedContext);

            Response.Write($"{Log("非同步工作 已經完成")}<br/><hr/>");
        }
        catch (Exception e)
        {
            Response.Write($"{Log($"Error {e.Message}")}<br/>");
        }

        return new HttpStatusCodeResult(200);
    }

    string Log(string msg)
    {
        var synchronizationContext = System.Threading.SynchronizationContext.Current;
        var taskScheduler = TaskScheduler.Current;
        var httpContextCurrent = System.Web.HttpContext.Current;
        return $"{msg} <ul><li>" +
            $"執行緒 Thread → {System.Threading.Thread.CurrentThread.ManagedThreadId} </li><li>" +
            $"工作排程 Task scheduler → {taskScheduler} </li><li>" +
            $"同步內文 SynchronizationContext → {synchronizationContext} </li><li>" +
            $"Http內文 Http Context → {httpContextCurrent} </li></ul>";
    }

    public ActionResult About()
    {
        ViewBag.Message = "Your application description page.";

        return View();
    }

    public ActionResult Contact()
    {
        ViewBag.Message = "Your contact page.";

        return View();
    }
}

關於 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月21日 星期二

在 WPF 使用 執行緒的同步內容 SynchronizationContext 進行非同步工作處理問題

在 WPF 使用 執行緒的同步內容 SynchronizationContext 進行非同步工作處理問題

SynchronizationContext類別是一個基底類別,它提供執行緒內容的同步處理需求,它可以幫助我們在在不同的同步情況下,進行進行非同步/同步操作正確行為。若想要更深入去了解 SynchronizationContext 這個類別功能,可以參考 不可或缺的 SynchronizationContext 文章。

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


了解更多關於 [同步處理原始物件概觀] 的使用方式

在這篇文章中,我們將要來說明在 WPF 使用 執行緒的同步內容 SynchronizationContext 進行非同步工作處理問題,也就是我們在 WPF 專案程式碼開發時候,必須要注意一個很重要的需求,任何想要變更 UI 相關物件或其屬性的程式碼,需要在 主執行緒 ( Main Thread ) 或稱 UI執行緒 ( UI Thread ) 下來執行,若沒有遵守這樣的要求,將會造成您的應用程式發生例外異常的問題。這樣的問題將會發生在您的 WPF 專案中有使用到 多執行緒 Multiple Thread 或者 非同步 Asynchronous 處理需求,若沒有特別注意,將會發生不可預期的例外異常問題。
我們來展示當您進行多執行緒或者非同步工作程式設計並且沒有遵循這樣要求,會發生甚麼樣的問題,以及要如何解決此一問題。在這裡您需要知道 WPF 有繼承 SynchronizationContext 類別,實作出 DispatcherSynchronizationContext 類別,當您在非主執行緒或者UI執行緒下執行程式碼,此時,需要在別的執行緒 Thread 下進行更新與變動 UI 相關物件屬性,您可以透過 DispatcherSynchronizationContext 物件幫助您做到同步內容,如此,相關 UI 異動程式碼就會在主執行緒下執行,也就是不會發生不明例外異常。
底下是我們要測試的 WPF XAML 頁面宣告,在這裡,我們宣告了兩個按鈕, [btnRunAsyncWithoutSynchronizationContext] / [btnRunAsyncWithSynchronizationContext] ,分別要進行沒有使用同步內容與有使用同步內容的測試。底下為執行後的畫面。
WPF SynchronizationContext
XAML
<Window x:Class="執行緒的同步處理內容WPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:執行緒的同步處理內容WPF"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="60"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <TextBlock
            Grid.Row="0" Grid.ColumnSpan="2"
            x:Name="tbkResult"
            Text="執行結果"
            TextWrapping="Wrap"
            HorizontalAlignment="Center" VerticalAlignment="Center"
            FontSize="30"
            />

        <Button
            Grid.Row="1"
            x:Name="btnRunAsyncWithoutSynchronizationContext"
            Content="執行非同步工作,沒有同步內容" Click="btnRunAsyncWithoutSynchronizationContext_Click"/>

        <Button
            Grid.Row="1" Grid.Column="1"
            x:Name="btnRunAsyncWithSynchronizationContext"
            Content="執行非同步工作,有同步內容" Click="btnRunAsyncWithSynchronizationContext_Click"/>
    </Grid>
</Window>
現在,我們來看看當點選這兩個按鈕後,會發生甚麼問題呢?
  • btnRunAsyncWithoutSynchronizationContext
    當點選這個按鈕之後,將會進行執行非同步工作,沒有同步內容,我們可以從程式碼中看出,當這按鈕點選之後,將會執行這個敘述
    await new System.Net.Http.HttpClient().GetStringAsync("http://www.google.com").ConfigureAwait(false)
    這表示將會讀取 Google 網站上的內容,並且傳回該網頁字串內容,不過,在這裡 我們有呼叫 ConfigureAwait(false),這表示當這個非同步工作執行完成之後,並不會回到現在這個執行緒,而是會從執行池 ThreadPool 內取得一個執行緒,繼續執行剩下的程式碼。關於 ConfigureAwait 說明,可以參考 ConfigureAwait 網頁
    請在 tbkResult.Text = foo; 敘述上設定中斷點,並且執行這個專案,接著點選 [執行非同步工作,沒有同步內容] 按鈕,這個時候將會停留在這個中斷點上。請在監看式視窗中,加入觀察 BeforeSynchronizationContext , BeforeThreadID , AfterSynchronizationContext , AfterThreadID ,結果該會類似下面截圖。
    在這裡我們看到在進行非同步工作方法執行之前,當時是執行緒 ID 是 1 (也就是在主執行緒上,我們可以從下面的執行緒視窗看的出來) ,而且當時 SynchronizationContext.Current 屬性中,是有 System.Windows.Threading.DispatcherSynchronizationContext 這個型別的物件;不過,當非同步工作執行完成後,繼續回到這個事件方法內,我們發現到了,這個時候的執行緒ID變成了 7,並且是一個背景工作執行緒,而且這個時候 SynchronizationContext.Current 屬性內已經變成空值。
    WPF Thread
    請打開 [除錯] > [視窗] > [執行緒] 視窗,我們可以看到這個應用程式有用到的執行緒相關資訊,在這個視窗中,我們看到了 主執行緒 Main Thread / UI 執行緒 UI Thread 是編號為 1 的執行緒,而我們停留在中斷點上的程式碼,則是在執行緒為編號 7 的執行緒上執行。
    WPF Thread
    若在此時我們想要修改任何 UI 控制項的屬性,就會發生問題。您將會得到這個錯誤訊息,這也表示了我們的應用程式將會中止執行了。
    System.InvalidOperationException: '呼叫執行緒無法存取此物件,因為此物件屬於另一個執行緒。'
    System.InvalidOperationException
  • btnRunAsyncWithSynchronizationContext
    當點選這個按鈕之後,將會進行執行非同步工作,有同步內容,我們可以從程式碼中看出,當這按鈕點選之後,將會執行這個敘述
    await new System.Net.Http.HttpClient().GetStringAsync("http://www.google.com");
    這表示將會讀取 Google 網站上的內容,並且傳回該網頁字串內容,不過,在這裡 我沒有呼叫 ConfigureAwait(false),這表示當這個非同步工作執行完成之後,會回到現在這個執行緒,也就是主執行緒或者UI執行緒。
    請在 [btnRunAsyncWithSynchronizationContext_Click] 事件方法內的 tbkResult.Text = foo; 敘述上設定中斷點,並且執行這個專案,接著點選 [執行非同步工作,有同步內容] 按鈕,這個時候將會停留在這個中斷點上。請在監看式視窗中,加入觀察 BeforeSynchronizationContext , BeforeThreadID , AfterSynchronizationContext , AfterThreadID ,結果該會類似下面截圖。
    在這裡我們看到在進行非同步工作方法執行前、後,都是位於執行緒 ID 是 1 (也就是在主執行緒上,我們可以從下面的執行緒視窗看的出來) ,=而且當時 SynchronizationContext.Current 屬性中,都是有 System.Windows.Threading.DispatcherSynchronizationContext 這個型別的物件。
    WPF Thread
    請打開 [除錯] > [視窗] > [執行緒] 視窗,我們可以看到這個應用程式有用到的執行緒相關資訊,在這個視窗中,我們看到了 主執行緒 Main Thread / UI 執行緒 UI Thread 是編號為 1 的執行緒,而我們停留在中斷點上的程式碼,則是在執行緒為編號 1 的執行緒上執行。
    WPF Thread
    若在此時我們想要修改任何 UI 控制項的屬性,就不會發生問題。為什麼呢?
    因為這些程式碼都是在主執行緒上執行的呀
    底下是執行後的程式畫面截圖
    SynchronizationContext
C Sharp / C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace 執行緒的同步處理內容WPF
{
    /// <summary>
    /// MainWindow.xaml 的互動邏輯
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void btnRunAsyncWithoutSynchronizationContext_Click(object sender, RoutedEventArgs e)
        {
            SynchronizationContext BeforeSynchronizationContext = SynchronizationContext.Current;
            int BeforeThreadID = Thread.CurrentThread.ManagedThreadId;
            var foo = await new System.Net.Http.HttpClient().GetStringAsync("http://www.google.com").ConfigureAwait(false);
            SynchronizationContext AfterSynchronizationContext = SynchronizationContext.Current;
            int AfterThreadID = Thread.CurrentThread.ManagedThreadId;
            tbkResult.Text = foo;
        }

        private async void btnRunAsyncWithSynchronizationContext_Click(object sender, RoutedEventArgs e)
        {
            SynchronizationContext BeforeSynchronizationContext = SynchronizationContext.Current;
            int BeforeThreadID = Thread.CurrentThread.ManagedThreadId;
            var foo = await new System.Net.Http.HttpClient().GetStringAsync("http://www.google.com");
            SynchronizationContext AfterSynchronizationContext = SynchronizationContext.Current;
            int AfterThreadID = Thread.CurrentThread.ManagedThreadId;
            tbkResult.Text = foo;
        }
    }
}


了解更多關於 [使用 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 課程