none
跨執行緒作業無效: 存取控制項 'textBox1' 時所使用的執行緒與建立控制項的執行緒不同。 RRS feed

  • 問題

  •  Dear all:

                   想請叫大家為什麼textBox1.Text = textBox1.Text + szData;這段會出錯呢?什麼是跨執行緒作業無效?這問題要怎麼解決?

     

     

    Code Snippet
    public void OnDataReceived(IAsyncResult asyn)
            {
                try
                {
                    CSocketPacket theSockId = (CSocketPacket)asyn.AsyncState;
                    //end receive...
                    int iRx = 0;
                    iRx = theSockId.thisSocket.EndReceive(asyn);
                    char[] chars = new char[iRx + 1];
                    System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
                    int charLen = d.GetChars(theSockId.dataBuffer, 0, iRx, chars, 0);
                    System.String szData = new System.String(chars);
                    textBox1.Text = textBox1.Text + szData;
                    WaitForData(m_socWorker);
                }
                catch (ObjectDisposedException)
                {
                    System.Diagnostics.Debugger.Log(0, "1", "\nOnDataReceived: Socket has been closed\n");
                }
                catch (SocketException se)
                {
                    MessageBox.Show(se.Message);
                }
            }

     

    2007年4月23日 上午 07:30

解答

  • try this:

     

    1.把 textBox1.Text = textBox1.Text + szData; 那行改以下列程式碼取代:

    Code Snippet
    MethodInvoker mi = new MethodInvoker(this.UpdateUI);
    this.BeginInvoke(mi, null);

     

    2. 增加一個 UpdateUI method:

    Code Snippet
    private void UpdateUI()
    {
      textBox1.Text = textBox1.Text + szData;
    }

     

    請注意:你要把 szData 改成類別的 data member,否則以上 code 無法編譯。

     

    2007年4月23日 上午 07:52
  • Code Snippet
    delegate void SetTextCallback();
            private string szData;

     

            public void OnDataReceived(IAsyncResult asyn)
            {
                try
                {
                    CSocketPacket theSockId = (CSocketPacket)asyn.AsyncState;
                    //end receive...
                    int iRx = 0;
                    iRx = theSockId.thisSocket.EndReceive(asyn);
                    char[] chars = new char[iRx + 1];
                    System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
                    int charLen = d.GetChars(theSockId.dataBuffer, 0, iRx, chars, 0);
                    this.szData = new System.String(chars);
               
                    MethodInvoker mi = new MethodInvoker(this.UpdateUI);
                    this.BeginInvoke(mi, null);
                    WaitForData(m_socWorker);
                }
                catch (ObjectDisposedException)
                {
                    System.Diagnostics.Debugger.Log(0, "1", "\nOnDataReceived: Socket has been closed\n");
                }
                catch (SocketException se)
                {
                    MessageBox.Show(se.Message);
                }
            }

            private void UpdateUI()
            {
                textBox1.Text = textBox1.Text + szData;
            }

     

    2007年4月23日 下午 01:47
    版主

所有回覆

  • 若你有開新執行緒,但又企圖存取主執行緒(使用者介面在的執行緒)時,就會發生這個錯誤。

     

    你應該要實作一個中間的交換層,可以由全域變數來實作,新的執行緒用它來與主執行緒交換資料。

    主執行緒則是定時偵測這個交換層,若交換層的資料發生變更時,就更新使用者介面。(這可以用事件來實作)

     

    BackgroundWorker 就是用這樣的方式交換二個執行緒間的資料的。

    2007年4月23日 上午 07:47
    版主
  • try this:

     

    1.把 textBox1.Text = textBox1.Text + szData; 那行改以下列程式碼取代:

    Code Snippet
    MethodInvoker mi = new MethodInvoker(this.UpdateUI);
    this.BeginInvoke(mi, null);

     

    2. 增加一個 UpdateUI method:

    Code Snippet
    private void UpdateUI()
    {
      textBox1.Text = textBox1.Text + szData;
    }

     

    請注意:你要把 szData 改成類別的 data member,否則以上 code 無法編譯。

     

    2007年4月23日 上午 07:52
  • 請教Michael Tsai:

    1.szData 改成類別的 data member...這不懂..

     

    2.private void UpdateUI()
         {
             textBox1.Text = textBox1.Text + szData;   

       }

         szData放在這的話上面的System.String szData = new System.String(chars);跟上面的有關係阿..拿下來的話就不能用了
          錯誤 1 名稱 'szData' 不存在於目前內容中....

      

    2007年4月23日 上午 08:45
  • 改成這樣吧

    Code Snippet
    MethodInvoker mi = new MethodInvoker(this.UpdateUI);
    this.BeginInvoke(mi, new object[]{szData});

     

    Code Snippet
    private void UpdateUI(string szData)
    {
      textBox1.Text = textBox1.Text + szData;
    }

     

    2007年4月23日 上午 09:19
  • Hi 速沛小子:

    這樣不行。MethodInvoker 的 delegate 不能帶參數,也不能帶傳回值。除非...自己定義一個 delegate 型別。

     

    To 小豆芽:

    我所說的「data member」,也就是類別的成員變數,可能我用的辭彙跟你不同吧。參考以下範例:

     

    Code Snippet

    class MyClass

    {

        private string szData;  // data member

     

        private void UpdateUI() // member function

        {

            ........

        }

    }

     

    我想這種方法對你來說比較快,建議先試試看。若能解決你的問題,可以朝速沛小子建議的方向來改善(需自訂委派型別)。

    參考文章:http://cht.gotdotnet.com/quickstart/howto/doc/WinForms/WinFormsThreadMarshalling.aspx

    也建議你搜尋一些執行緒的文章來閱讀。

     

     

    2007年4月23日 上午 10:20
  •  Dear Michael:

                            我還是有點不懂妳們說的方式...因為我寫了一段System.String szData = new System.String(chars);這段的chars又跟上一段有關係而上一段又跟上一段有關係...所以聽妳們說講解完後...不知道要從哪邊下手

     

    Code Snippet

           delegate void SetTextCallback();
            private string szData;

     

            public void OnDataReceived(IAsyncResult asyn)
            {
                try
                {
                    CSocketPacket theSockId = (CSocketPacket)asyn.AsyncState;
                    //end receive...
                    int iRx = 0;
                    iRx = theSockId.thisSocket.EndReceive(asyn);
                    char[] chars = new char[iRx + 1];
                    System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
                    int charLen = d.GetChars(theSockId.dataBuffer, 0, iRx, chars, 0);
                    System.String szData = new System.String(chars);
               
                    MethodInvoker mi = new MethodInvoker(this.UpdateUI);
                    this.BeginInvoke(mi, null);
                    WaitForData(m_socWorker);
                }
                catch (ObjectDisposedException)
                {
                    System.Diagnostics.Debugger.Log(0, "1", "\nOnDataReceived: Socket has been closed\n");
                }
                catch (SocketException se)
                {
                    MessageBox.Show(se.Message);
                }
            }

            private void UpdateUI()
            {
                textBox1.Text = textBox1.Text + szData;
            }

     

     

    2007年4月23日 下午 01:34
  • Code Snippet
    delegate void SetTextCallback();
            private string szData;

     

            public void OnDataReceived(IAsyncResult asyn)
            {
                try
                {
                    CSocketPacket theSockId = (CSocketPacket)asyn.AsyncState;
                    //end receive...
                    int iRx = 0;
                    iRx = theSockId.thisSocket.EndReceive(asyn);
                    char[] chars = new char[iRx + 1];
                    System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
                    int charLen = d.GetChars(theSockId.dataBuffer, 0, iRx, chars, 0);
                    this.szData = new System.String(chars);
               
                    MethodInvoker mi = new MethodInvoker(this.UpdateUI);
                    this.BeginInvoke(mi, null);
                    WaitForData(m_socWorker);
                }
                catch (ObjectDisposedException)
                {
                    System.Diagnostics.Debugger.Log(0, "1", "\nOnDataReceived: Socket has been closed\n");
                }
                catch (SocketException se)
                {
                    MessageBox.Show(se.Message);
                }
            }

            private void UpdateUI()
            {
                textBox1.Text = textBox1.Text + szData;
            }

     

    2007年4月23日 下午 01:47
    版主
  • 看了小朱的回帖,才發現我有一個細節漏了說(我以為很明顯)...

    把 szData 宣告成類別成員後,原本的區域變數 szData 就不用再宣告,直接取用類別成員就行了。然而你寫的那段程式既宣告了類別成員 szData,又有區域變數 szData,這樣的話,程式執行時雖然不會出現你原發問時的錯誤,但執行結果不會正確(因為指定 szData 值的時候是指定給區域變數,而 UpdateUI 裡面用的卻是類別成員的 szData)。

     

    小朱已經把完整的程式碼貼出來了,請參考他的就 ok 了。

     

    另外,我看到你貼上來的程式碼的第一行,有一個 SetTextCallback 委派型別。如果你想直接用速沛小子建議的方法,則可以參考以下程式碼,特別要注意的地方已經用不同顏色顯示。

    Code Snippet

     

    public class YourClass
    {
        delegate void SetTextCallback(string s);

     

       public void OnDataReceived(IAsyncResult asyn)
        {
            try
            {
                CSocketPacket theSockId = (CSocketPacket)asyn.AsyncState;
                //end receive...
                int iRx = 0;
                iRx = theSockId.thisSocket.EndReceive(asyn);
                char[] chars = new char[iRx + 1];
                System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
                int charLen = d.GetChars(theSockId.dataBuffer, 0, iRx, chars, 0);
                System.String szData = new System.String(chars);
                
                // 用 SetTextCallback 取代原本的 MethodInvoker
                SetTextCallback stc = new SetTextCallback(this.UpdateUI);
                this.BeginInvoke(mi, szData); // 注意這裡的寫法,有點神奇,不是嗎?
                WaitForData(m_socWorker);
            }
            catch (ObjectDisposedException)
            {
                System.Diagnostics.Debugger.Log(0, "1", "\nOnDataReceived: Socket has been closed\n");
            }
            catch (SocketException se)
            {
                MessageBox.Show(se.Message);
            }
        }

       
        private void UpdateUI(string s) 
        {
            textBox1.Text = textBox1.Text + szData;
        }

    }

     

    Note: callback 函式(UpdateUI)必須完全符合 SetTextCallback 委派型別的 signature(傳回值的型別、參數的個數與型別)。

    2007年4月23日 下午 02:38
  • Dear all:

                  我再修改完Code後當我Server傳送訊息給Client可以收到訊息了,可是當Server第二傳送後就發生Client的UI TextBox當掉反白,這問題困惱我滿久的....希望有哪一個大大能幫我解決~萬分感謝

     

    Code Snippet

    private void Form1_Load(object sender, EventArgs e)
            {
                try
                {
                    string ip = Dns.GetHostAddresses(Dns.GetHostName())[0].ToString();
                    IPAddress myIP = IPAddress.Parse(ip);

                    m_socListener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                    IPEndPoint ipLocal = new IPEndPoint(myIP, 1234);
                   
                    m_socListener.Bind(ipLocal);
                    //start listening...
                    m_socListener.Listen(4);
                    // create the call back for any client connections...
                    m_socListener.BeginAccept(new AsyncCallback(OnClientConnect), null);

                }
                catch (SocketException se)
                {
                    MessageBox.Show(se.Message);
                }
            }

     

            public void OnClientConnect(IAsyncResult asyn)
            {
                try
                {
                    m_socWorker = m_socListener.EndAccept(asyn);

                    WaitForData(m_socWorker);
                }
                catch (ObjectDisposedException)
                {
                    System.Diagnostics.Debugger.Log(0, "1", "\n OnClientConnection: Socket has been closed\n");
                }
                catch (SocketException se)
                {
                    MessageBox.Show(se.Message);
                }

            }

     

            public class CSocketPacket
            {
                public System.Net.Sockets.Socket thisSocket;
                public byte[] dataBuffer = new byte[1];
            }

     

            public void WaitForData(System.Net.Sockets.Socket soc)
            {
                try
                {
                    if (pfnWorkerCallBack == null)
                    {
                        pfnWorkerCallBack = new AsyncCallback(OnDataReceived);
                    }
                    CSocketPacket theSocPkt = new CSocketPacket();
                    theSocPkt.thisSocket = soc;
                    // now start to listen for any data...
                    soc.BeginReceive(theSocPkt.dataBuffer, 0, theSocPkt.dataBuffer.Length, SocketFlags.None, pfnWorkerCallBack, theSocPkt);
                }
                catch (SocketException se)
                {
                    MessageBox.Show(se.Message);
                }

            }

     

           delegate void SetTextCallback();
            private string szData;

           public void OnDataReceived(IAsyncResult asyn)
            {
                try
                {
                    CSocketPacket theSockId = (CSocketPacket)asyn.AsyncState;
                    //end receive...
                    int iRx = 0;
                    iRx = theSockId.thisSocket.EndReceive(asyn);
                    char[] chars = new char[iRx + 1];
                    System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
                    int charLen = d.GetChars(theSockId.dataBuffer, 0, iRx, chars, 0);
                    this.szData = new System.String(chars);

                    MethodInvoker mi = new MethodInvoker(this.UpdateUI);

                    this.Invoke(mi, new object[] { chars });

                    //this.BeginInvoke(mi, null);
                    WaitForData(m_socWorker);
                }
                catch (ObjectDisposedException)
                {
                    System.Diagnostics.Debugger.Log(0, "1", "\nOnDataReceived: Socket has been closed\n");
                }
                catch (SocketException se)
                {
                    MessageBox.Show(se.Message);
                }
            }

     

            private void UpdateUI()
            {
                textBox1.Text = textBox1.Text + szData;
            }

     

     

    2007年4月26日 上午 02:07
  • 加入以下這行即可

    Form.CheckForIllegalCrossThreadCalls = false;

    2016年3月3日 上午 12:34
  • 加入以下這行即可

    Form.CheckForIllegalCrossThreadCalls = false;

    你有注意看 MSDN 關於這個屬性的說明嗎 ? 微軟並不建議在正式的 production code  使用此一屬性. 這也就是為什麼我們不拿這個出來討論的原因 . 而且超過兩個以上的執行緒處理 UI , 有可能會因為沒有處理好競爭或死結, 於是整個程式死當.

    來自 Control.CheckForIllegalCrossThreadCalls 屬性

    當控制項的建立執行緒以外的執行緒嘗試存取該控制項的其中一個方法或屬性時,它通常會導致無法預期的結果。常見的無效執行緒活動為在可存取控制項的 Handle 屬性之錯誤執行緒上呼叫。將 CheckForIllegalCrossThreadCalls 設定為 true可以在偵錯時更容易找到並診斷這種執行緒活動。請注意,如果在偵錯工具之外啟動應用程式,不當的跨執行緒呼叫總是會引發例外狀況。


    在現實生活中,你和誰在一起的確很重要,甚至能改變你的成長軌跡,決定你的人生成敗。 和什麼樣的人在一起,就會有什麼樣的人生。 和勤奮的人在一起,你不會懶惰; 和積極的人在一起,你不會消沈; 與智者同行,你會不同凡響; 與高人為伍,你能登上巔峰。


    2016年3月3日 上午 05:58
    版主