20181103_C#執行緒初探, BeginInvoke_EndInvoke
在C#中學習多執行緒之前, 必須要深刻的理解委託; 基本上所有的多執行緒都在靠委託來完成
一. 程序和執行緒:
a) 程序和執行緒都是計算機的概念, 跟程式語言沒有任何關係
b) 程序和執行緒都屬於計算機作業系統自身控制和排程, 程式語言只有使用的份, 最終的控制權還是得作業系統說了算, 程式語言最多有提醒功能, 比如叫執行緒休眠/掛起/終止, 至於作業系統聽不聽, 做不做, 什麼時候做, 那是作業系統高興不高興的事.
c) 在工作管理員中, 下圖每一個都是一個程序
d) 在工作管理員中, 效能中可以看到執行緒:
e) 程序: 一個程式執行時, 佔用計算機所有資源的總和; (CPU/記憶體/磁碟/GPU/IO)
f) 執行緒: 程式執行流的最小單位, 任何操作的執行都是由執行緒完成的, 執行緒是依託於程序存在的, 一個程序可以包含多個執行緒, 執行緒也可以有自己的計算資源
g) 多執行緒: 多個執行流同時執行
二. 同步非同步多執行緒:
a) 同步和非同步都是對方法的描述
i. 同步:
- 在一個方法體內一步一步的按照程式碼編寫的順序(分支/迴圈)來依次執行;
- 同步方法卡介面,主(UI)執行緒忙於計算
- 同步方法慢,只有一個執行緒幹活
- action.Invoke();→屬於同步呼叫
ii. 非同步:
- 不會等待方法的完成,會直接進入下一行 非阻塞;
- 非同步多執行緒方法不卡介面,主執行緒完事兒了,計算任務交給子執行緒在做;
- 非同步多執行緒方法快,因為多個執行緒併發運算
- 應用: winform提升使用者體驗;web一個業務操作後要發郵件,非同步傳送郵件 ; action.BeginInvoke()→屬於非同步呼叫
iii. 多執行緒, 啟動無序, 執行時間不確定, 結束無序
三. 同步非同步和多執行緒:
a) 同步非同步是方法執行的概念
b) 執行緒/程序屬於計算機概念
四. 示例和程式碼:
所有演示都用到的DoSomething方法程式碼如下:
/// <summary> /// 一個比較耗時耗資源的私有方法 /// </summary> /// <param name="name"></param> private void DoSomethingLong(string name) { Console.WriteLine($"****************DoSomethingLong {name} Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************"); long lResult = 0; for (int i = 0; i < 1000000000; i++) { lResult += i; } Console.WriteLine($"****************DoSomethingLong {name} End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************"); }
a) BeginInvoke屬於非同步呼叫, 當任務完成之後, 呼叫CallBack指定的動作; 下面程式碼演示
Action<string> action = this.DoSomethingLong; IAsyncResult asyncResult = null; //非同步回撥 AsyncCallback callback = ia => { //Console.WriteLine(object.ReferenceEquals(asyncResult, ia)); //true //Console.WriteLine(ia.AsyncState); //ia.AsyncState 這個值就是傳遞過來的hao; 可以看做是引數 //Console.WriteLine($"到這裡計算已經完成了。{Thread.CurrentThread.ManagedThreadId.ToString("00")}。"); }; //對於BeginInvoke的理解: 相當於.net框架為我們做了一個小封裝, 當使用BeginInvoke的時候, .net框架從執行緒池, 拿一個執行緒出來, 然後啟動來執行beginInvoke, 當beginInvoke執行完成之後, 產生一個變數(asyncResult), 作為一個結果來描述這個執行緒, 然後這個變數又會作為一個引數來呼叫callback並執行, 所以當你使用object.ReferenceEquals(asyncResult, ia)發現結果是個true //後面這個hao, 表示狀態引數, 也就是說如果要在回撥的時候使用某些資訊, 則可以通過這個引數進行傳遞 asyncResult = action.BeginInvoke("btnAsyncAdvanced_Click", callback, "hao"); Console.WriteLine($"到這裡計算已經完成了。{Thread.CurrentThread.ManagedThreadId.ToString("00")}。");
b) 啟動多執行緒去平行計算, 但是主執行緒又得真的等待到主執行緒把事情做完之後, 才能返回計算結果, 進行後續的執行, 下面程式碼演示使用asyncResult.IsCompleted完成等待:
IAsyncResult asyncResult = null; //非同步回撥 AsyncCallback callback = ia => { Console.WriteLine($"到這裡計算已經完成了。{Thread.CurrentThread.ManagedThreadId.ToString("00")}。"); }; asyncResult = action.BeginInvoke("btnAsyncAdvanced_Click", callback, "hao"); Console.WriteLine($"到這裡計算已經完成了。{Thread.CurrentThread.ManagedThreadId.ToString("00")}。"); int i = 0; //IsCompleted判斷非同步操作是否完成 while (!asyncResult.IsCompleted)//1 卡介面:主執行緒忙於等待 { //可以等待,邊等待邊做其他操作 ; 做一點使用者提示 //可能最多200ms的延遲 if (i < 10) { Console.WriteLine($"檔案上傳完成{i++ * 10}%..");//File.ReadSize } else { Console.WriteLine($"檔案上傳完成99.9%.."); } Thread.Sleep(200); } Console.WriteLine($"上傳成功了。。。..");
c) 下面的程式碼演示使用asyncResult.AsyncWaitHandle.WaitOne(1000);進行等待
Action<string> action = this.DoSomethingLong; IAsyncResult asyncResult = null; //非同步回撥 AsyncCallback callback = ia => { Console.WriteLine("我在callback裡. . ."); }; asyncResult = action.BeginInvoke("btnAsyncAdvanced_Click", callback, "hao"); Thread.Sleep(300); Console.WriteLine("執行其他的程式碼. . ."); Console.WriteLine("執行其他的程式碼. . ."); Console.WriteLine("執行其他的程式碼. . ."); asyncResult.AsyncWaitHandle.WaitOne();//等待任務的完成 ; 上面的方式檢視狀態的等待事件是否完成, 這種方式是通過訊號量來實現的 asyncResult.AsyncWaitHandle.WaitOne(-1);//等待任務的完成; -1這個引數和不傳遞引數是一樣的, 不傳遞引數也表示一直等待, 傳遞-1也表示一直等待 asyncResult.AsyncWaitHandle.WaitOne(1000);//限時等待;但是最多等待1000ms; 比如呼叫遠端介面, 超過什麼時間就不再等待
d) 下面的程式碼演示使用EndInvoke來等待, 並獲取返回值
Func<int> func = () => { Thread.Sleep(2000); return DateTime.Now.Day; }; Console.WriteLine($"func.Invoke()={func.Invoke()}"); IAsyncResult asyncResult1 = func.BeginInvoke(r => { func.EndInvoke(r); //通常來講, EndInvoke會解除安裝BeginInvoke的裡面, 表示只調用一次; 另外注意: EndInvoke只能呼叫一次. 這一行和下面一行只能存在一個 Console.WriteLine(r.AsyncState); }, "冰封的心"); //Console.WriteLine($"func.EndInvoke(asyncResult)={func.EndInvoke(asyncResult1)}");
e) 三種執行緒等待方式的總結:
//1. asyncResult.IsCompleted 可以邊等待, 邊做其他的事情 //2. asyncResult.AsyncWaitHandle.WaitOne(1000); 可以做限時等待 //3. action.EndInvoke(asyncResult)可以做返回值等待