2019年11月24日 星期日

.NET C# 單執行緒 同步 多執行緒 非同步 執行緒同步 Synchronization 邏輯處理器數量 設計探討 : Part 2 在多執行緒下,非同步執行加一與減一方法,造成執行緒不安全的現象

.NET C# 單執行緒 同步 多執行緒 非同步 執行緒同步 Synchronization 邏輯處理器數量 設計探討 : Part 2 在多執行緒下,非同步執行加一與減一方法,造成執行緒不安全的現象

  1. .NET C# 單執行緒 同步 多執行緒 非同步 執行緒同步 Synchronization 邏輯處理器數量 設計探討 : Part 1 在單一執行緒下,同步執行加一與減一方法
  2. .NET C# 單執行緒 同步 多執行緒 非同步 執行緒同步 Synchronization 邏輯處理器數量 設計探討 : Part 2 在多執行緒下,非同步執行加一與減一方法,造成執行緒不安全的現象
  3. .NET C# 單執行緒 同步 多執行緒 非同步 執行緒同步 Synchronization 邏輯處理器數量 設計探討 : Part 3 在多執行緒下,使用 lock 關鍵字來做到執行緒安全
  4. .NET C# 單執行緒 同步 多執行緒 非同步 執行緒同步 Synchronization 邏輯處理器數量 設計探討 : Part 4 在多執行緒下,使用 Interlocked 來做到執行緒安全
  5. .NET C# 單執行緒 同步 多執行緒 非同步 執行緒同步 Synchronization 邏輯處理器數量 設計探討 : Part 5 在多執行緒下,不要全部都使用執行緒同步機制 來做到執行緒安全

    從上一篇文章中,.NET C# 單執行緒 同步 多執行緒 非同步 執行緒同步 Synchronization 邏輯處理器數量 設計探討 : Part 1 在單一執行緒下,同步執行加一與減一方法 中,將會看到若使用同步程式設計方式所建置出來的程式,設計上相單的簡單,而且不容易出錯,可以,卻無法充分的善用電腦上的處理器數量,達到並行處理的效果,也就是可以提升整體應用程式的執行下能;在加一與減一的程式碼中,大約需要花費 7.5 秒的時間才能夠完成。
    那麼,要怎麼才能夠設計出可以具備平行處理的程式碼,充分善用這些處理器來同時執行所設計的程式碼呢?在 .NET / C# 程式語言中可以透過 Thread 這個類別,建立起額外的執行緒,讓這些執行緒可以具備並行執行能力。
    對於要使用執行緒類別可以說相當的容易,建立一個 Thread 執行個體,在建構函式內指派一個委派方法,接著呼叫該 Thread 執行緒執行個體的 Start() 方法就可以做到;可是,若沒有真正理解到執行緒背後的技術原理與可能造成的問題,將會帶來所設計的程式碼不是很容易偵錯,因為,在多執行緒執行環境下,想要知道多執行緒程式碼所造成的問題,是無法很容易地在設計階段輕鬆地看出問題端倪,必須要等到程式執行的時候,才能夠看到問題發生;另外就是,每增加一個執行緒,將會需要額外的成本花費,例如:建置一個執行緒需要額外的記憶體與建置時間、過多的執行緒存在於系統上的時候,將會有許多 CPU 處理時間花費在 內容交換 Context Switch 上,也就是說,在設計多執行緒程式碼的時候,不是一昧的產生出很多的執行緒就可以提稱整體執行效能,畢竟,這台電腦上有幾個處理器核心,同一個時間就僅能夠執行這些處理器數量的執行緒程式碼,其他過多的執行緒就僅能夠在排程器 Scheduler 的佇列 Queue 中進行等待,所以,對於多執行緒的程式設計,需要有較多的關注與設計經驗。
    在此文章中,將會把加一與減一這兩個方法,設計在不同的執行緒下來執行,而且每次迴圈的執行結果,還是會更新到共用靜態變數上;最後,將來看看這樣的設計方式是否可以達到正確的執行結果與提升執行效能的好處。
    在這篇文章所提到的專案原始碼,可以從 GitHub 下載

    進行多執行緒的測試,但是不具備執行緒安全特性

    在這裡將會設計一個新的方法, AsyncAddSub , 在這個方法內將會建立兩個執行緒,底下為這個新方法的程式碼列表。
    C Sharp / C#
    static void AsyncAddSub(AddSubAction addSubAction)
    {
         WaitHandle[] waitHandles = new WaitHandle[]
        {
            new AutoResetEvent(false),
            new AutoResetEvent(false)
        };
       AddSub addSub = new AddSub();
        Thread thread1 = new Thread(x =>
        {
            addSub.Adds(addSubAction);
            (waitHandles[0] as AutoResetEvent).Set();
        });
        Thread thread2 = new Thread(x =>
        {
            addSub.Subs(addSubAction);
            (waitHandles[1] as AutoResetEvent).Set();
        });
    
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        thread1.Start();
        thread2.Start();
    
        WaitHandle.WaitAll(waitHandles);
        stopwatch.Stop();
        Console.WriteLine($"Counter={AddSub.counter}, {stopwatch.ElapsedMilliseconds:N0}ms");
    }
    
    在 AsyncAddSub 方法內,將會使用使用 Thread thread1 = new Thread(x => {...}); 這樣的敘述建立起兩個執行緒,其中這兩個執行緒要執行的委派方法將會使用 Lambda 匿名委派方法來宣告,對於 thread1 這個執行緒,將會呼叫 AddSub 類別所建立的執行個體內的 Adds 方法,而對於 thread2 這個執行緒,將會呼叫 AddSub 類別所建立的執行個體內的 Subs 方法。
    接著,將會使用 thread1.Start(); & thread2.Start(); 這兩個敘述,分別來啟動這兩個執行。傳統上,要知道執行緒是否執行完成,可以執行 thread1.Join(); 或者 thread2.Join(); 方法,不過,當執行 Thread.Join 方法的時候,將會造成當前執行緒進入 Block 封鎖階段,直到該執行緒執行完成之後,才會繼續執行底下的程式碼;然而,在多執行緒執行環境下,開發者是無法確認哪一個執行緒一定會先執行完畢,而就使用該執行緒的 Join 方法來等待,就算先啟動執行的執行緒,也並不代表這個執行緒會先執行完畢。
    現在產生一個問題,要如何知道這兩個執行緒都已經完成了,這裡將會引進了執行緒同步 Synchronizaiton Constructor ,這裡將會使用執行緒同步的 WaitHandl 陣列來儲存兩個 AutoResetEvent 物件,而對於預設的 AutoResetEvent 物件將會預設設定其為 false,表示初始狀態設定為未收到信號。
    當加一或者減一執行緒執行完畢之後,將會呼叫 AutoResetEvent.Set() 方法,將事件的狀態設定為收到信號,在這裡將會表示該執行緒已經執行完成的訊號通知;而在啟動這兩個執行緒之後的程式碼,將會使用 WaitHandle.WaitAll(waitHandles); 敘述來等到這兩個執行緒都已經全部完成,如此,就會接下來執行底下的程式碼。
    如同上面的程式碼,在此設計了一個類別 AddSub ,該類別提供了兩個執行個體方法與一個靜態變數,第一個物件方法 Adds 會執行一個迴圈,將會跑 int.MaxValue 次,每次回圈內將會執行 counter++ 這個表示式,這裡的 counter 是一個靜態變數,但是 counter 的數值異動卻是由執行個體方法來去變更的;第二個物件方法 Subs 會執行一個迴圈,將會跑 int.MaxValue 次,每次回圈內將會執行 counter-- 這個表示式。也就是說 Adds() 這個方法將會再回圈內每次都進行加一的動作,而對於 Subs() 這個方法將會在回圈內每次都進行減一的動作。
    現在,已經完成了兩個執行緒的程式碼設計,而且可以計算這兩個執行緒都全部執行完畢之後所需要的時間,這裡就可以使用這個敘述 ThreadSynchronization yes NoLock 10000000 ,在第一個引數中使用 yes 文字,指定測試程式要使用多執行緒方式來執行,並且不要使用任何的執行緒同步建構子 Synchronizaiton Constructor 來保護要存取共用的靜態變數程式碼。
    底下將會指定在多執行緒下,非同步執行加一與減一方法,並且指定在不同的邏輯處理器數量下的執行結果。
    ThreadSynchronization yes NoLock 10000000
    Counter=0, 7,417ms
    
    ThreadSynchronization yes NoLock 10100000
    Counter=878337449, 7,105ms
    
    ThreadSynchronization yes NoLock 10101000
    Counter=66089567, 14,481ms
    
    ThreadSynchronization yes NoLock 11000000
    Counter=896913837, 6,765ms
    
    ThreadSynchronization yes NoLock 11100000
    Counter=-13693011, 9,710ms
    
    ThreadSynchronization yes NoLock 11110000
    Counter=-6874487, 9,235ms
    
    ThreadSynchronization yes NoLock 11111111
    Counter=-66814967, 9,337ms
    

    發現問題1 : 僅有一個結果是正確的

    從上面的執行結果可以看滿詭異的現象,除了第一個測試 ThreadSynchronization yes NoLock 10000000 所顯示的 Counter 靜態變數數值為 0,其他的都不是為 0,這也就表示了僅有第一個測試方法 ThreadSynchronization yes NoLock 10000000 可以得到正確且預期的執行結果,這裡指定僅使用單一一個邏輯處理器來進行這一的程式碼執行;而對於指定使用多個邏輯處理器的執行結果,都是不正確的。
    ThreadSynchronization yes NoLock 10000000
    Counter=0, 7,456ms
    
    ThreadSynchronization yes NoLock 00100000
    Counter=0, 7,478ms
    
    ThreadSynchronization yes NoLock 00001000
    Counter=0, 7,525ms
    
    ThreadSynchronization yes NoLock 00000010
    Counter=0, 7,592ms
    
    ThreadSynchronization yes NoLock 01000000
    Counter=0, 7,608ms
    
    ThreadSynchronization yes NoLock 00010000
    Counter=0, 7,462ms
    
    ThreadSynchronization yes NoLock 00000100
    Counter=0, 7,521ms
    
    ThreadSynchronization yes NoLock 00000001
    Counter=0, 7,781ms
    
    在這裡,將會重複執行 ThreadSynchronization yes NoLock 10000000 這樣的測試,並且只會指定一個邏輯處理器來同時使用並行方式來執行這兩個執行緒,發現到結果都是相同的,執行結果是正確的,而且,所花費的時間與使用單一執行緒的同步執行方式差不多,大約要花費 7.5秒的時間。
    +

    不過,雖然這裡使用了多執行緒方式的方式來執行,可以卻因為僅有一個處理器可以使用,雖然執行結果是正確的,發現到是沒有任何可以提升執行效能的幫助。

    發現問題2 : 多邏輯處理器會發生執行結果是錯的

    當指定了兩個以上的邏輯處理器來執行這個加一與減一的多執行緒程式,其共通的結果就是,最終的計算結果數值是錯誤的,因此,只要結果是不正確的,就算程式跑得再快,也是沒有用的。
    不過,從上面的執行結果發現到,在指定使用 10101000 3 個邏輯處理器的時候,竟然會比使用 10100000 邏輯處理器所花費的時間更多,竟然花費多達一倍以上的時間,不過,當使用了 11000000 這樣的兩個邏輯處理器的時候,卻得到僅需要 6.7 秒的時間,雖然這樣的執行效能比起同步執行結果少了 1 秒鐘,但是,因為結果是不正確的,跑得再快也沒用。
    另外,也發現到同樣的指定 3 個邏輯處理器 ,這裡使用 11100000 比起使用 10101000 明顯快多了(但是,他們的執行結果都是不正確的),這樣產生的問題,歡迎大家可以在留言版中,留下各位的看法,進行討論看看。
    最後,就算使用了所有的 8 個邏輯處理器,11111111,執行速度也沒有比起同步單一執行緒的執行速度快,而且這樣的結果也是不正確的。



    2019年11月23日 星期六

    .NET C# 單執行緒 同步 多執行緒 非同步 執行緒同步 Synchronization 邏輯處理器數量 設計探討 : Part 1 在單一執行緒下,同步執行加一與減一方法

    .NET C# 單執行緒 同步 多執行緒 非同步 執行緒同步 Synchronization 邏輯處理器數量 設計探討 : Part 1 在單一執行緒下,同步執行加一與減一方法

    在這篇文章所提到的專案原始碼,可以從 GitHub 下載

    進行單一執行緒的測試

    在我這台電腦主機上,使用的是 Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz,其具有四核心與超執行緒 Hyper Threading 的技術,因此,對於作業系統而言,將會看到這台電腦具有八核心的能力。
    在這裡,將會進行這樣的計算作業,也就是 加一 +1 與 減一 -1
    C Sharp / C#
    class AddSub
    {
        public static int counter = 0;
        public void Adds()
        {
            for (int i = 0; i < int.MaxValue; i++)
            {
                counter++;
            }
        }
        public void Subs()
        {
            for (int i = 0; i < int.MaxValue; i++)
            {
                counter--;
            }
        }
    }
    
    如同上面的程式碼,在此設計了一個類別 AddSub ,該類別提供了兩個執行個體方法與一個靜態變數,第一個物件方法 Adds 會執行一個迴圈,將會跑 int.MaxValue 次,每次回圈內將會執行 counter++ 這個表示式,這裡的 counter 是一個靜態變數,但是 counter 的數值異動卻是由執行個體方法來去變更的;第二個物件方法 Subs 會執行一個迴圈,將會跑 int.MaxValue 次,每次回圈內將會執行 counter-- 這個表示式。也就是說 Adds() 這個方法將會再回圈內每次都進行加一的動作,而對於 Subs() 這個方法將會在回圈內每次都進行減一的動作。
    現在,首先要進行的測試目的為將會是要進行僅有一個執行緒的執行結果測試
    C Sharp / C#
    static void SyncAddSub()
    {
        AddSub addSub = new AddSub();
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        addSub.Adds();
        addSub.Subs();
        stopwatch.Stop();
        Console.WriteLine($"Counter={AddSub.counter}, {stopwatch.ElapsedMilliseconds:N0}ms");
    }
    
    透過上面的程式碼,設計出 SyncAddSub() 方法,在這個方法內,將會使用 Stopwatch 型別提供計算執行所需要花費時間工具,當計時器開始運作的時候,便會開始同步執行 addSub.Adds(); & addSub.Subs(); 這兩個加一與減一計算工作,最後將會顯示 AddSub.counter 這個靜態變數的值為多少 (當然,正確的結果應該是要為 0) 和所耗費的時間,單位式 ms。
    由於大多數的電腦大都具有多CPU或者多核心的能力,也就是說在這台電腦上將會有多個處理器可以使用,如此,便可以採用平行處理的方式,大幅提升整體電腦執行效能。
    為了要了解到上面設計的單一執行緒下執行的程式,若在具有不同數量的處理器下來執行,會有甚麼樣的變化,在這裡將會透過 Process.GetCurrentProcess().ProcessorAffinity = (IntPtr) 0b10000000 ; 這樣的敘述,來指定這次執行的 .NET 程式,僅能夠限制使用多少顆處理器數量,從這個例子中,經會限制這次執行的程式,僅能夠使用一顆處理器。
    為了要方便測試,這裡將會把所有需要測試的步驟,都設計在同一個專案程式碼內,ThreadSynchronization,透過者個專案程式,僅需要在執行的時候,提供不同的引數,就可以做到不同的執行效果,底下是使用方式說明
    使用方式 : ThreadSynchronization 多執行緒模式 計算方式 使用CPU模式
    多執行緒模式 : Yes , No
    計算方式 : NoLock , UserModeLock , UsingNETLock , NoLockByLocal
    使用CPU模式 : 1000000 , 11000000 , 10100000 , 11110000 , 10101000
    
    第一個參數將會指定是否要使用多執行緒的方式來執行測試,也就是加一這個方法將會在執行緒 Thread A 中來執行,而減一這個方法將會安排在執行緒 Thread B 中來執行;若在此提供的參數值為 no,則表示加一與減一這兩個方法,都會在同一個執行緒來執行。
    第二個參數將會指定要使用哪種 執行緒 同步 Thread Synchronization 機制來設計出執行緒安全 Thread Safe 的程式碼,也就是同樣的程式碼,在多執行緒環境下跑出來的結果,不論執行幾次,執行結果都是可以預期的,也就是結果將會是正確的,沒有模稜兩可問題產生;其中 NoLock 將會不使用任何的執行緒同步機制,當然,將會有可能造成執行緒不安全的問題、UserModeLock 將會使用 User Mode 使用者模式下的執行緒同步機制,這裡將會使用 Interlocked 這個類別來做到、UsingNETLock 這個參數指定使用 C# lock 關鍵字來做到關鍵區域 Critical Section 關鍵區域在同一個時間內,僅會有一個執行緒可以執行這段程式碼、NoLockByLocal 將會在多執行緒執行過程中,當在迴圈跑的時候,不會去變更共用靜態變數,而是在迴圈跑完之後,才會來將累加結果,更新到共用靜態變數內,而在這裡將會透過 Interlocked 這個類別來做到 執行緒安全 的目的。
    第三個參數則是用來指定所要使用的處理器 Processor 的數量,要使用這個參數是要確認,當成是於單執行緒 同步執行 或者多執行緒 非同步執行下,若使用不同的處理器數量,是否會有造成甚麼影響;當使用了 Process.GetCurrentProcess().ProcessorAffinity = (IntPtr) 0b10000000 ; 這個敘述,表示指定使用該核心內的一個處理器來進行運算,並不是使用到電腦上所有可用的處理器,而當使用了 Process.GetCurrentProcess().ProcessorAffinity = (IntPtr) 0b11000000 ; ,則設定使用同一個處理器硬體核心,不過,對於這裡指定兩個處理器,將會採用 Hyper-Threading 技術來運行,根據 Intel 的說明,當採用 Hyper-Threading 技術,可以提升約 20% ~ 30% 的執行效能;若使用了 Process.GetCurrentProcess().ProcessorAffinity = (IntPtr) 0b10100000 ; 敘述,則是指定了兩個核心處理器作為此次程式要運算的處理器。
    現在,請將這篇文章所提到的範例程式,請先使用 Release 建置模式來建置這個專案,接著,下達這樣的指令 ThreadSynchronization no NoLock 10000000
    這裡將會先來檢測僅使用單一執行緒來進行加一與減一計算工作,不過,這裡將會指定使用不同的處理器數量,底下是測試結果(在不同的電腦上,因為 CPU 硬體規格不盡相同,所以,可能需要依據讀者本身的可用處理器數量,自行調整第三個參數的內容。
    想要確認可用處理器數量,可以打開 Windows 電腦的工作管理員,切換到 [效能] 標籤頁次,在該頁次右下方將會看到 [邏輯處理器] 這個文字標籤的右邊,就是這台電腦上可用的處理器數量,在這台電腦上實際上為單一一顆CPU硬體,但是具有 4 Core 四核心,而且每個核心都有採用 Hyper Threading 的技術,因此,對於整體電腦而言,將會有 8 顆處理器數量,也就是這裡所提到的 邏輯處理器 數量。
    底下將會指定在單一執行緒下,同步執行加一與減一方法,並且指定在不同的邏輯處理器數量下的執行結果。
    ThreadSynchronization no NoLock 10000000
    Counter=0, 7,515ms
    
    ThreadSynchronization no NoLock 10100000
    Counter=0, 7,477ms
    
    ThreadSynchronization no NoLock 10101000
    Counter=0, 7,458ms
    
    ThreadSynchronization no NoLock 11000000
    Counter=0, 7,410ms
    
    ThreadSynchronization no NoLock 11100000
    Counter=0, 7,468ms
    
    ThreadSynchronization no NoLock 11110000
    Counter=0, 7,497ms
    
    ThreadSynchronization no NoLock 11111111
    Counter=0, 7,670ms
    
    從上面的執行結果可以看的出來,若在單一執行緒下,同步執行加一與減一方法,最終的計算結果,也就是 Counter 這個靜態變數的內容將都會是 0,這是正確與可以預期的結果,而且不論使用多少的邏輯處理器數量,所得到的結果都是相當的;這也就是說,若設計的程式採用單一執行緒的同步方式來執行,將不會有執行緒不安全的問題,而且執行上所花費的時間將不會明顯的影響。



    2019年10月31日 星期四

    Github 快速上手 2 : 建立新的 Repository 與 同步

    Github 快速上手 2 : 建立新的 Repository 與 同步

    在 Github 建立一個新的 Repository

    請打開 Github 網頁,於登入認證之後,點選右上方的 + 加號 圖片
    當 [Create a new repository] 頁面出現之後,請在 [Repository name] 這個欄位中,輸入想要建立的 Repository 的名稱,這裡會輸入 MyFirstRepository
    使用滑鼠捲動網頁到最下方,將會看到 [Create respository] 這個按鈕,請點選這個按鈕,完成這個 Repository 的建立。

    複製 Github 的 Repository 到本機電腦上

    現在完成了這個 MyFirstRepository 這個 Repository 的建立,現在,請在 [Quick setup — if you’ve done this kind of thing before] 標題的下方,將會看到一個 HTTS URL,請將這個 URL 複製到系統剪貼簿內。
    請打開檔案總管,切換到想要儲存這個 Repository 的目錄內,這裡是切換到這台電腦的 [C:\Xamarin] 目錄內;接著,在檔案總管空白地方,使用滑鼠右擊,現在將會看到彈出功能表中有個 [Git Clone...] 這個選項,請點選這個選項。
    現在, [TortoiseGit] 將會使用系統剪貼簿內儲存的 URL,複製一份遠端 Github 上的 Repository 到這台電腦上的這個目錄內。
    當出現了 [Git clone] 對話窗的時候,可以直接使用預設值 ( 因為,對於 URL 欄位的內容,將會從剪貼簿取得 ),接著請點選 [OK] 按鈕

    設定 Repository 簽入 Commit 要排出的檔案類型

    現在請切換到這個 Repository 的目錄下,也就是 [C:\Xamarin\MyFirstRepository] 這個目錄,並且建立一個 [.gitignore] 文字檔案。
    該 [.gitignore] 的檔案內容,可以從 https://raw.githubusercontent.com/vulcanlee/CSharp2019/master/.gitignore 取得
    上面螢幕截圖為建立 [.gitignore] 文字檔案 與 加入該檔案內容的操作結果。
    現在可以進行第一次的 簽入 Commit,請在這個 Repository 目錄內的空白地方,點選滑鼠右鍵,從彈出功能表中選擇 [Git Commit -> "master"] 這個選項
    當 [C:\Xamarin\MyFirstRepository - Commit - TortoiseGit] 對話窗出現之後,請在 [Message] 欄位中輸入 Init (或者任何代表此次簽入的文字內容)。
    在下方的 [Changes made (double-click on file for diff)] 標題下方,點選 [All] 文字,代表要選擇所有當前所有的檔案。
    最後,找到下方的 [Commit] 按鈕,請先點選該按鈕右方的 向下黑色三角形 ,此時會出現一個彈出選單,請在該選單內選擇 [Commit & Push] ,表示,按下這個按鈕之後,將會進行簽入與推入到遠端的 Github 主機內。
    當進行第一次操作的時候,將會如上面螢幕截圖,要求第一次要先進行 Github 身分驗證,在這裡,請輸入剛剛申請 Github 的帳號(或者Email)和密碼之後,點選 [Github Login] 對話窗下方的 [Login] 按鈕。
    當看到如上面螢幕截圖,這就表示這次的簽入與推入到遠端 Github Repository 的工作已經完成了。
    從網頁中將會看到剛剛增加的 [.gitignore] 檔案已經成功地簽入到 Github Repository 內了。

    將 Xamarin.Forms 的方案內所有專案,簽入到 Github Repository 內

    現在可以找出一個正在開發或者新建立的 Xamarin.Forms 專案,不過,最好這個專案是可以正常建置與執行的,因為,這裡需要這些專案內的 Bin Obj 目錄下的這些二進位元檔案。
    在這裡已經複製一份 CustNaviService 之 Xamarin.Forms 的方案,根據檔案總管的統計,這個 CustNaviService 目錄下總共有 182MB 磁碟空間,可是,真正的原始碼並沒有這麼多,絕大多數是由二進位檔案所佔據。
    在此之前,已經 簽入 Commit 這個 [.gitignore] 檔案,因此,當要繼續簽入這個專案檔案內容的時候,將會排除這些 二進位 型態的檔案,現在,來檢驗看看。
    請在檔案總管確認是在 [C:\Xamarin\MyFirstRepository] 目錄下,使用滑鼠右鍵點選空白地方,當彈出功能表出現的時候,請選擇 [Git Commit -> "master"] 這個選項,準備簽入與推入到 Github Repository 內
    在出現了 [C:\Xamarin\MyFirstRepository - Commit - TortoiseGit] 對話窗的時候,請在 [Message] 欄位內輸入 [加入 CustNaviService 方案所有檔案] 簽入備註訊息,並且在下方點選 [All] 文字,選擇所有的檔案內容。
    若此時使用滑鼠捲動最下方的檔案清單內容,應該不會看到任何 二進位元 類型的檔案。
    最後,請點選 [Commit & Push] 按鈕,進行簽入與推入到Github Repository。
    當完成簽入與推入之後,將會看到如上圖的畫面。

    確認推入的專案,將不包含二進位元的檔案

    先打開 Github 網站中的 MyFirstRepository Repository,檢查剛剛推入的檔案是否已經推進去了。
    打開 https://github.com/XamarinForms/MyFirstRepository 網頁,在下方就會看到 [CustNaviService] 目錄已經在 Github 的 Repository 內了。
    請使用滑鼠來點選上面截圖的紅色箭頭指向的按鈕,也就是 [Clone or download] 按鈕
    當彈出了 [Clone with HTTPS] 視窗的時候,請將該子視窗內的 URL 複製起來。這裡的 URL 將會是 : https://github.com/XamarinForms/MyFirstRepository.git
    現在請使用檔案總管切換到 C:\Xamarin 這個目錄下
    在該目錄的空白地方,使用滑鼠右鍵點選 [Git Clone...] 這個選項
    當出現了 [Git clone - TortoiseGit] 這個對話窗,請將 [Directory] 這個欄位,輸入成為 C:\Xamarin\MyFirstRepository2
    也就是要複製一份 Github 上的 Repository,不過,是要複製到 C:\Xamarin\MyFirstRepository2 這個目錄下 (原先剛剛的 Repository 是複製到 C:\Xamarin\MyFirstRepository 目錄下)
    最後,請點選 [OK] 按鈕,開始進行複製
    完成複製後,請在 [C:\Xamarin - Git Command Program - TortoiseGit] 對話窗內,點選 [Close] 按鈕
    從上面操作螢幕截圖中,可以看到 [MyFirstRepository2] 這個目錄已經產生出來了。
    請使用檔案總管,查看 [C:\Xamarin\MyFirstRepository2\CustNaviService] 目錄的內容,發現到 [CustNaviService] 這個目錄下,僅佔有 1.32 MB 大小,這與之前查詢 [C:\Xamarin\MyFirstRepository\CustNaviService] 目錄所站的空間為 182MB 有很大的落差,這是因為絕大部分的檔案都是二進位元的檔案,而且在簽入的時候都已經排出掉了,不會簽入到 Github Repository 內。

    清除本機 Repository 內的二進位元檔案

    現在,請使用檔案總管回到 [C:\Xamarin\MyFirstRepository] 目錄下,準備把這些二進位元的檔案清除掉,順便檢查清除後的專案所佔據的磁碟空間。
    請在 [C:\Xamarin\MyFirstRepository] 目錄下,使用滑鼠右擊空白地方,當彈出選單出現之後,依據點選 [TortoiseGit] > [Clean up...] 選項
    現在出現了 [C:\Xamarin\MyFirstRepository - Clean - TortoiseGit] 這個對話窗,相關的選項請使用預設值,直接點選 [OK] 按鈕
    現在請使用檔案總管來查看 [C:\Xamarin\MyFirstRepository\CustNaviService] 目錄的內容,從上面螢幕截圖中,可以看到,這些二進位元檔案也都被移除了,整個 [C:\Xamarin\MyFirstRepository\CustNaviService] 目錄僅佔有 1.32 MB
    請注意
    在使用這個功能的時候,請記得這個 Repository 已經完成了 簽入 Commit 動作