淺談Invoke 和 BegionInvoke的用法

不少人對Invoke和BeginInvoke理解不深入,不知道該怎麼應用,在這篇博文裏將詳細闡述Invoke和BeginInvoke的用法:編程

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

1.Control中Invoke,BeginInvoke多線程

2.Delegate中Invokke,BeginInvoke異步

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

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");
        }

運行結果爲:ui

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

再來看看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. 若是後臺任務要求必須按照特定順序執行,或者必須訪問共享資源,則異步編程不適合,而應該直接採用多線程開發技術。

相關文章
相關標籤/搜索