3

I am a beginner in high level programming languages. I am trying to make an WForms app for a serial port , im using VS 2010 C#

I get the following error:

Cross-thread operation not valid: Control 'rtxtDataArea' accessed from a thread other than the thread it was created on.

This happens here:

private void ComPort_DataReceived_1(object sender, SerialDataReceivedEventArgs e)
    {
         recievedData = ComPort.ReadExisting(); //read all available data in the receiving buffer.

        // Show in the terminal window 
        rtxtDataArea.ForeColor = Color.Green;    // error , 
        rtxtDataArea.AppendText(recievedData + "\n");
    }

I have tried to change the color of a textbox when I receive some data. It fires that cross thread error.

The question is why it does not fire the same error here, when I try to change the color of a label?

private void btnConnect_Click(object sender, EventArgs e)
    {
        if (ComPort.IsOpen)
        {
            disconnect();
        }
        else
        {
            connect();
            rdText.ForeColor = Color.Blue;//ok, it works
        }
    }

; this works ; the first does not.

Why? Is not the ComPort_DataReceived_1 the same nature as btnConnect_Click ? Or what is the reason?

I have read a lot about threads, but I understood nothing I can use, Can someone give an intuitive explanation ?

Martin Evans
  • 45,791
  • 17
  • 81
  • 97
fjohn
  • 33
  • 5
  • this should help http://stackoverflow.com/questions/142003/cross-thread-operation-not-valid-control-accessed-from-a-thread-other-than-the?rq=1 – Prashant Majhwar Jul 20 '16 at 12:39
  • `ComPort_DataReceived_1` and `btnConnect_Click` are both (very likely) event handlers. But beyond that, you cannot assume anything about one of them based on the other. In particular, here, the fact that one is observed to always be called on the UI thread implies nothing about the other. – Damien_The_Unbeliever Jul 20 '16 at 12:44

2 Answers2

2

In winforms there is only one thread that may change anything on the UI like enable buttons, change text boxes, etc. Usually this is the UI thread. Quite often this is the only thread you have.

However, if you start a new thread, this thread might want to change the UI. This happens especially if this new thread fires an event that is received by your form.

Whenever you see the message accessed from a thread other than the thread it was created on, you can be almost certain this is the case.

The most easy solution to solve this is using the functions Control.IsInvokeRequired and Control.Invoke. The pattern to do this is as follows. The following function updates myButton on myForm

private void UpdateMyButton (MyType myParameter)
{
    if (myButton.InvokeRequired)
    {   // this is not the thread that created the button to update
        this.Invoke(new MethodInvoker( () => this.UpdateMyButton(myParameter)));
        // this will let the UI thread call this function with the same parameter.
    }
    else
    {   // Now called by the UI thread: this thread may change myButton
        myButton.Enabled = myParameter.ShouldButtonEnable;
        myButton.Text = myParameter.ButtonText;
    }
}

By the way, if you have to update several controls on your form you ought to check InvokeRequired for each of these controls. However, since they are usually created by the same UI thread it is sufficient to check for this.InvokeRequired.

Control.Invoke returns after the invoke is completed, so after all items are updated. Upon return of Invoke you can use the result of UpdateMyButton.

If you don't want your non-ui thread to wait for completion of UpdateMyButton, consider the use of Control.BeginInvoke: "hey UI thread, whenever you've got time, can you UpdateMyButton for me. Of course in that case you can't use the results of UpdateMyButton

Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116
  • I did not started a new thread (i am not sure what a thread is in fact). my app will change other elements in it often . i have quite many elements (buttons , labels, text boxes, etc) . for each element should I make a special function like this ? this double my app code size. – fjohn Jul 20 '16 at 16:36
  • A thread is something like the one who processes your code. It is used do some processing while your thread can continue doing its own processing. Google for "thread explained". As a test debug code like: bool b = this.InvokeRequired; where you get your exception. It should not be required. If it is, then somewhere a thread is started. Maybe code from others you use? – Harald Coppoolse Jul 21 '16 at 06:36
1

Because "DataReceived" runs on another thread and not UI thread. You must use Invoke for that :

private void ComPort_DataReceived_1(object sender, SerialDataReceivedEventArgs e)
{
  recievedData = ComPort.ReadExisting(); //read all available data in the receiving buffer.

  if (InvokeRequired)
  {
    // If not on UI thread, then invoke
    Invoke(new MethodInvoker(() =>
    {
      // Show in the terminal window 
      rtxtDataArea.ForeColor = Color.Green; // error , 
      rtxtDataArea.AppendText(recievedData + "\n");
    }));
  }
  else
  {
    // On UI thread, invoke not needed
    // Show in the terminal window 
    rtxtDataArea.ForeColor = Color.Green; // error , 
    rtxtDataArea.AppendText(recievedData + "\n");
  }
}