1. 程式人生 > >淺談Invoke 和 BegionInvoke的用法

淺談Invoke 和 BegionInvoke的用法

很多人對Invoke和BeginInvoke理解不深刻,不知道該怎麼應用,在這篇博文裡將詳細闡述Invoke和BeginInvoke的用法:

首先說下Invoke和BeginInvoke有兩種用法:

1.Control中Invoke,BeginInvoke

2.Delegate中Invokke,BeginInvoke

這兩種情況是不同的,我們首先講一下第一種,也就是Control類中的Invoke,BeginInvoke的用法。我們先來看一下MSDN是如何解釋的:

control.invoke(引數delegate)方法:在擁有此控制元件的基礎視窗控制代碼的執行緒上執行指定的委託。

control.begininvoke(引數delegate)方法:在建立控制元件的基礎控制代碼所線上程上非同步執行指定委託。

通過官方的解釋,我們大概覺得 Invoke是同步的,而BeginInvoke是非同步的,至於兩者的實際差別,我們通過程式碼來展示:

  Thread invokeThread;
        public delegate void invokeDelegate();
        private void button1_Click(object sender, EventArgs e)
        {
            //MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "AAA");
            Console.WriteLine("AAA");
            invokeThread = new Thread(new ThreadStart(StartMethod));
            invokeThread.Start();
            string a = string.Empty;
            for (int i = 0; i < 3; i++)      //調整迴圈次數,看的會更清楚
            {
                Thread.Sleep(1000);
                a = a + "B";
            }
           // MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + a);

            Console.WriteLine(a);
        }
        private void StartMethod()
        {
           // MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "CCC");
            Console.WriteLine("CCC");
            button1.Invoke(new invokeDelegate(invokeMethod));
            //MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "DDD");
            Console.WriteLine("DDD");
            Console.Read();
        }

        private void invokeMethod()
        {
            //MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "EEE");

            Console.WriteLine("EEE");
        }

執行結果為:

從結果可以看出執行時首先執行AAA,然後CCC和BBB同時執行,Invoke將Invoke方法提交給主執行緒,主執行緒執行完執行在Invoke上的方法,然後才執行子執行緒的方法,簡單講同步就是說必須等帶Invoke上的方法執行完,才能執行子執行緒的方法。

再來看看BeginInvokke的執行邏輯:

   Thread invokeThread;
        public delegate void invokeDelegate();
        private void button1_Click(object sender, EventArgs e)
        {
            //MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "AAA");
            Console.WriteLine("AAA");
            invokeThread = new Thread(new ThreadStart(StartMethod));
            invokeThread.Start();
            string a = string.Empty;
            for (int i = 0; i < 3; i++)      //調整迴圈次數,看的會更清楚
            {
                Thread.Sleep(1000);
                a = a + "B";
            }
           // MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + a);

            Console.WriteLine(a);
        }
        private void StartMethod()
        {
           // MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "CCC");
            Console.WriteLine("CCC");
            button1.BeginInvoke(new invokeDelegate(invokeMethod));
            //MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "DDD");
            Console.WriteLine("DDD");
            Console.Read();
        }

        private void invokeMethod()
        {
            //MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "EEE");

            Console.WriteLine("EEE");
        }

執行的結果為:

從結果可以看出,首先執行AAA,然後CCC和BBB同時執行,由於採用了BeginInvoke方法,即所謂的非同步執行,子執行緒不再等待主執行緒執行完成,所以DDD輸出,主執行緒執行完BBB再執行EEE.

從執行的結果來看不管時Invoke提交的方法,還是BeginInvoke提交的方法,都是在主線上執行的,所以同步和非同步的概念是相對於子執行緒來說的,在invoke例子中我們會發現invoke所提交的委託方法執行完成後,才能繼續執行 DDD;在begininvoke例子中我們會發現begininvoke所提交的委託方法後,子執行緒講繼續執行DDD,不需要等待委託方法的完成。 那麼現在我們在回想下invoke(同步)和begininvoke(非同步)的概念,其實它們所說的意思是相對於子執行緒而言的,其實對於控制元件的呼叫總是由 主執行緒來執行的。我們很多人搞不清這個同步和非同步,主要還是因為我們把參照物選錯了。其實有時候光看概念是很容易理解錯誤的,Invoke會阻塞主支執行緒,BeginInvoke只會阻塞主執行緒,不會阻塞支執行緒!因此BeginInvoke的非同步執行是指相對於支執行緒非同步,而不是相對於主執行緒非同步 。現在是不是徹底搞清楚兩者的區別啦。

解決不是從建立控制元件的執行緒訪問它

在多執行緒程式設計中,我們經常要在工作執行緒中去更新介面顯示,而在多執行緒中直接呼叫介面控制元件的方法是錯誤的做法,Invoke 和 BeginInvoke 就是為了解決這個問題而出現的,使你在多執行緒中安全的更新介面顯示。

正確的做法是將工作執行緒中涉及更新介面的程式碼封裝為一個方法,通過 Invoke 或者 BeginInvoke 去呼叫,兩者的區別就是一個導致工作執行緒等待,而另外一個則不會。

由於歷史原因,形成了以下幾種寫法:

            //第一種
            if (this.textBox1.InvokeRequired)
            {
                this.textBox1.Invoke(new EventHandler( delegate { this.textBox1.Text = "測試"; }));
            }

            //第二種
            this.Invoke(new EventHandler(delegate { this.textBox1.Text = "測試"; }));


            //第三種
            this.Invoke(new Action(() => { this.textBox1.Text = "測試"; }));

毫無疑問,第三種呼叫方法是最佳選擇。

 

delegate中的Invoke方法和BeginInvoke方法

delegate中Invoke方法和BenginInvoke方法主要是用在同步呼叫,非同步呼叫,非同步回撥上

A.同步呼叫,舉個簡單的例子:

  private void button2_Click(object sender, EventArgs e)
        {
            Add d = new Add((a, b) => 
            {
                Thread.Sleep(5000);
                return a + b; 
            }); //例項化委託並給委託賦值

            int result = d.Invoke(2, 5);


            Console.WriteLine("繼續做別的事情");

            Console.WriteLine(result.ToString());
            Console.Read();

        }

        
        public delegate int Add(int i1,int i2);

Invoke方法的引數很簡單,一個委託,一個引數表(可選),而Invoke方法的主要功能就是幫助你在UI執行緒上呼叫委託所指定的方法。Invoke方法首先檢查發出呼叫的執行緒(即當前執行緒)是不是UI執行緒,如果是,直接執行委託指向的方法,如果不是,它將切換到UI執行緒,然後執行委託指向的方法。不管當前執行緒是不是UI執行緒,Invoke都阻塞直到委託指向的方法執行完畢,然後切換回發出呼叫的執行緒(如果需要的話),返回。
所以Invoke方法的引數和返回值和呼叫他的委託應該是一致的

B.非同步呼叫

舉個簡單的例子:

  public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //委託型別的Begininvoke(<輸入和輸出變數>,AsyncCallbac callback,object asyncState)方法:非同步呼叫的核心   
            //第一個引數10,表示委託對應的方法實參。
            //第二個引數callback,回撥函式,表示非同步呼叫結束時自動呼叫的函式。
            //第三個引數asyncState,用於向回撥函式提供相關引數資訊
            //返回值:IAsyncResult -->非同步操作狀態介面,封裝了非同步執行的中的引數
            //IAsyncResult介面成員:檢視幫助文件
            Calcute objCalcute = new Calcute(ExcuteDelegate1);

            IAsyncResult result = objCalcute.BeginInvoke(1, 2, null, null); //IAsyncResult是一個介面

            this.textBox1.Text = "等待結果.......";

            this.textBox2.Text = ExcuteDelegate2(20, 10).ToString();

             //委託型別的EndInvoke()方法:藉助於IAsyncResult介面物件,不斷查詢非同步呼叫是否結束。
             //該方法知道被非同步呼叫的方法所有引數,所有,非同步呼叫完畢後,取出非同步呼叫結果作為返回值。

this.textBox1.Text = objCalcute.EndInvoke(result).ToString();


        }

        public delegate int Calcute(int i1, int i2);

        int ExcuteDelegate1(int i1, int i2)
        {
            Thread.Sleep(5000);
            return i1 + i2;
        }

        int ExcuteDelegate2(int i1, int i2)
        {
            return i1 * i2;
        }
    }

從執行結果可以看出執行BeginInvoke上執行的方法時不會影響 this.textBox2.Text = ExcuteDelegate2(20, 10).ToString(); 的執行,但是EndInvoke方法需要等待執行緒執行完畢,所以依舊會阻塞主執行緒的執行,為了解決這個問題,非同步回撥就出現了,其實可以將非同步執行看成是非同步回撥的特殊情況

C.非同步回撥

看下面的例子:

 public FrmCalllBack()
        {
            InitializeComponent();

            //【3】初始化委託變數
            this.objMyCal = new MyCalculator(ExecuteTask);

            //也可以直接使用Lambda表示式
            this.objMyCal = (num, ms) =>
                {
                    System.Threading.Thread.Sleep(ms);
                    return num * num;
                };
        }
        //【3】建立委託變數(因為非同步函式和回撥函式都要用,所以定義成員變數)
        private MyCalculator objMyCal = null;

        //【1】宣告一個委託
        public delegate int MyCalculator(int num, int ms);

        /// <summary>
        /// 【2】根據委託定義一個方法:返回一個數的平方
        /// </summary>
        /// <param name="num">基數</param>
        /// <param name="ms">延遲的時間:秒</param>
        /// <returns></returns>
        private int ExecuteTask(int num, int ms)
        {
            System.Threading.Thread.Sleep(ms);
            return num * num;
        }

        //【4】同時執行多個任務
        private void btnExec_Click(object sender, EventArgs e)
        {
            for (int i = 1; i < 11; i++)//產生10個任務
            {
                //開始非同步執行,並封裝回調函式 objMyCal.BeginInvoke(10 * i, 1000 * i, null, i);
                objMyCal.BeginInvoke(10 * i, 1000 * i, MyCallBack, i);
                //最後一個引數 i 給回撥函式的欄位AsyncState賦值,如果資料很多可以定義成類或結構
            }
        }
        //【5】回撥函式
        private void MyCallBack(IAsyncResult result)
        {
            int res = objMyCal.EndInvoke(result);

            //非同步顯示結果:result.AsyncState欄位用來封裝回調時自定義的引數,object型別
            Console.WriteLine("第{0}個計算結果為:{1}", result.AsyncState.ToString(), res);
        }
    }

從執行結果看出,非同步回撥不再阻塞主執行緒的執行。注意: BeginInvoke和EndInvoke必須成對呼叫.即使不需要返回值,但EndInvoke還是必須呼叫,否則可能會造成記憶體洩漏。

 

非同步程式設計的總結:

1. 非同步程式設計是建立在委託基礎上的一種程式設計方法。
2. 非同步呼叫的每個方法都是在獨立的執行緒中執行。因此,本質上就是一種多執行緒程式,也可以說是一種“簡化版本”的多執行緒技術。
3. 比較適合在後臺執行較為耗費時間的"簡單任務",並且任務要求相互獨立,任務中不應該有程式碼直接訪問視覺化控制元件。
4. 如果後臺任務要求必須按照特定順序執行,或者必須訪問共享資源,則非同步程式設計不適合,而應該直接採用多執行緒開發技術。

&n