線程間操作無效: 從不是創建控件“button1”的線程訪問它。


.net2后是不能跨線程訪問控件的。,窗體上的控件是當前線程創建的,當用戶異步執行一個方法:在該方法中給窗體上的控件賦值,記住:當執行一個異步委托的時候,其實

就是開了一個線程去執行那個方法,這樣就會報錯:線程間操作無效: 從不是創建控件“某某某”的線程訪問它。

 

C# WinForm開 發中,這是一個比較常見的異常:線程間操作無效,從不是創建控件“xxx”的線程訪問它。這個異常來源於.NET2的一個限制:工作線程不能訪問窗口線程 創建的控件。解決方法主要有兩種,一種是在窗口線程中設置CheckForIllegalCrossThreadCalls = false ;另一種方式比較麻煩,使用委托的方式調用Invoke方法。

 public Form1()
        {
            InitializeComponent();
            Control.CheckForIllegalCrossThreadCalls = false;
        }

但以上不是推薦的方法。更好的辦法是用委托解決

  private void button1_Click(object sender, EventArgs e)
        {
            new Action(show).BeginInvoke(null, null);
        }

        void show()
        {
            //異步外的方法。這樣窗體不會假死
            while (true)
            {
                Thread.Sleep(2000);
                Action ac = new Action(showText);
                this.Invoke(ac); //在同步方法里面實現更新窗體上的數據
            }
        }

        /// <summary>
        /// 更新數據
        /// </summary>
        void showText()
        {
            richTextBox1.AppendText("更新\n");
        }

或者使用InvokeRequired屬性判斷

 /*
             // 摘要:
        //     獲取一個值,該值指示調用方在對控件進行方法調用時是否必須調用 Invoke 方法,因為調用方位於創建控件所在的線程以外的線程中。
        //
        // 返回結果:
        //     如果控件的 System.Windows.Forms.Control.Handle 是在與調用線程不同的線程上創建的(說明您必須通過 Invoke
        //     方法對控件進行調用),則為 true;否則為 false。

private void button1_Click(object sender, EventArgs e)
        {
            //new Action(show).BeginInvoke(null, null);
            new Action(show1).BeginInvoke(null, null);
        }
        void show1()
        {
            while (true)
            {
                Thread.Sleep(2000);//模擬等待效果
                show2();
            }
        }

        void show2()
        {
            //說明的當前外部線程
            /*
             // 摘要:
        //     獲取一個值,該值指示調用方在對控件進行方法調用時是否必須調用 Invoke 方法,因為調用方位於創建控件所在的線程以外的線程中。
        //
        // 返回結果:
        //     如果控件的 System.Windows.Forms.Control.Handle 是在與調用線程不同的線程上創建的(說明您必須通過 Invoke
        //     方法對控件進行調用),則為 true;否則為 false。
             */
            if (InvokeRequired)
            {
                /*既然是外部線程,那么就沒有權限訪問主線程上的控件
                 * 故要主線程訪問,開啟一個異步委托捆綁要執行的方法
                 * 交給主線程執行
                 */
                Action ac = new Action(show2);
                this.Invoke(ac); //這里執行后。則InvokeRequired就為false。因為此時已經是主線程訪問當前創建的控件
            }
            else
            {
                richTextBox1.AppendText("更新77\n");
            }
        }

 

看了第一段代碼是不是很不爽的感覺。showText()方法就一條賦值語句,則也獨立成一個方法。這里可以簡化代碼,用匿名函數或者用更簡單的lambda表達式(這個方法不需要其他用戶調用,就可以考慮用匿名函數)。那么看簡化后的代碼

  private void button1_Click(object sender, EventArgs e)
        {
            new Action(show).BeginInvoke(null, null);
        }

  void show()
        {
            //異步外的方法。這樣窗體不會假死
            while (true)
            {
                Thread.Sleep(2000);//在異步方法外實現等待,這樣窗體不會假死
                //Action ac = new Action(showText);
                //this.Invoke(ac); //在同步方法里面實現更新窗體上的數據

                //匿名函數
                Action at = new Action(delegate(){ richTextBox1.AppendText("更新\n"); });
                //lambda表達式更簡單
                Action at1 = new Action(()=> { richTextBox1.AppendText("更新\n"); });
                this.Invoke(at);

        //這里this。代表當前窗體(控件)上的線程執行此方法
                //這里不一定是this,只要是當前窗體上的控件都可以,比如
                // button1.Invoke(at);
                this.Invoke(at);//Invoke:在擁有此控件的基礎窗口句柄的線程上執行指定的委托。
                //richTextBox1.Invoke(at);
                //button1.Invoke(at);
} }

 

 我們用同樣的方法把InvokeRequired屬性判斷的那段代碼也用lambda表達式簡寫

 private void button1_Click(object sender, EventArgs e)
        {
            new Action(show1).BeginInvoke(null, null);
        }
  void show1()
        {
            //while (true)
            //{
            //    Thread.Sleep(2000);//模擬等待效果
            //    show2();
            //}

            for (int i = 0; i < 5000; i++)
            {
                Thread.Sleep(2000);//模擬等待效果
                //show2();

                if (InvokeRequired)
                {
                    Action ac = new Action(() => { richTextBox1.AppendText("更新767\n"); });
                    this.Invoke(ac); //這里執行后。則InvokeRequired就為false。因為此時已經是主線程訪問當前創建的控件
                }
            }

        }

 

 以上兩種方法都是實現相同的效果,差別就是多了一個InvokeRequired判斷是否需要執行Invoke()方法

 

參考:http://www.cnblogs.com/txw1958/archive/2012/08/21/2649192.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM