1. 程式人生 > >20181103_C#執行緒初探, BeginInvoke_EndInvoke

20181103_C#執行緒初探, BeginInvoke_EndInvoke

在C#中學習多執行緒之前, 必須要深刻的理解委託; 基本上所有的多執行緒都在靠委託來完成

一.   程序和執行緒:

a)         程序和執行緒都是計算機的概念, 跟程式語言沒有任何關係

b)         程序和執行緒都屬於計算機作業系統自身控制和排程, 程式語言只有使用的份, 最終的控制權還是得作業系統說了算, 程式語言最多有提醒功能, 比如叫執行緒休眠/掛起/終止, 至於作業系統聽不聽, 做不做, 什麼時候做, 那是作業系統高興不高興的事.

c)         在工作管理員中, 下圖每一個都是一個程序

d) 在工作管理員中, 效能中可以看到執行緒:

 

e)         程序: 一個程式執行時, 佔用計算機所有資源的總和; (CPU/記憶體/磁碟/GPU/IO)

f)         執行緒: 程式執行流的最小單位, 任何操作的執行都是由執行緒完成的, 執行緒是依託於程序存在的, 一個程序可以包含多個執行緒, 執行緒也可以有自己的計算資源

g)         多執行緒: 多個執行流同時執行

 

二.   同步非同步多執行緒:

a)         同步和非同步都是對方法的描述

i.              同步:

  1. 在一個方法體內一步一步的按照程式碼編寫的順序(分支/迴圈)來依次執行;
  2. 同步方法卡介面,主(UI)執行緒忙於計算
  3. 同步方法慢,只有一個執行緒幹活
  4. action.Invoke();→屬於同步呼叫

 ii.              非同步:

  1. 不會等待方法的完成,會直接進入下一行  非阻塞;
  2. 非同步多執行緒方法不卡介面,主執行緒完事兒了,計算任務交給子執行緒在做;
  3. 非同步多執行緒方法快,因為多個執行緒併發運算
  4. 應用: 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)可以做返回值等待