227

I want to send temperature value from a microcontroller using UART to C# interface and Display temperature on Label.Content. Here is my microcontroller code:

while(1) {
   key_scan(); // get value of temp
   if (Usart_Data_Ready())
   {
      while(temperature[i]!=0)
      {
         if(temperature[i]!=' ')
         {
            Usart_Write(temperature[i]);
            Delay_ms(1000);
         }
         i = i + 1;
      }
      i =0;
      Delay_ms(2000);
   }
}

and my C# code is:

private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
   txt += serialPort1.ReadExisting().ToString();
   textBox1.Text = txt.ToString();
}

but exception arises there "Cross-thread operation not valid: Control 'textBox1' accessed from a thread other than the thread it was created on" Please tell me how to get temperature string from my microcontroller and remove this Error!

M.R.T
  • 746
  • 8
  • 20
Fatima Zohra
  • 2,929
  • 2
  • 17
  • 17

6 Answers6

373

The data received in your serialPort1_DataReceived method is coming from another thread context than the UI thread, and that's the reason you see this error.
To remedy this, you will have to use a dispatcher as descibed in the MSDN article:
How to: Make Thread-Safe Calls to Windows Forms Controls

So instead of setting the text property directly in the serialport1_DataReceived method, use this pattern:

delegate void SetTextCallback(string text);

private void SetText(string text)
{
  // InvokeRequired required compares the thread ID of the
  // calling thread to the thread ID of the creating thread.
  // If these threads are different, it returns true.
  if (this.textBox1.InvokeRequired)
  { 
    SetTextCallback d = new SetTextCallback(SetText);
    this.Invoke(d, new object[] { text });
  }
  else
  {
    this.textBox1.Text = text;
  }
}

So in your case:

private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
  txt += serialPort1.ReadExisting().ToString();
  SetText(txt.ToString());
}
Chiramisu
  • 4,687
  • 7
  • 47
  • 77
Magnus Johansson
  • 28,010
  • 19
  • 106
  • 164
  • 1
    If you are having trouble with not having the InvokeRequired property on your control, try the parent form's InvokeRequired property. Use `if (this.InvokeRequired) { //SetTextCallBack etc. }` instead of `if (this.textBox1.InvokeRequired) { //SetTextCallBack etc. }` – Jroonk Dec 03 '14 at 20:44
  • 6
    will `control.BeginInvoke` work too? the solution can be 1 line too like this, right? `textbox1.BeginInvoke((MethodInvoker)delegate(){ textbox1.Text = txt.ToString(); });` – newbieguy Dec 10 '16 at 23:21
  • If anyone else is missing this (who is more familiar with Funcs and lambdas than delegates, like me), the way that `SetTextCallback` works to invoke `SetText` is that you pass in `SetText` to `new SetTextCallback()`. DUHHH. – ErikE Jan 22 '17 at 03:53
  • @newbieguy That worked for me. Question is, is it truly safe now? I get no error but that is a little above my understanding of what we are really dealing with here even after reading the MS article that Magnus provided. – MatthewD Feb 27 '19 at 16:43
  • 1
    @newbieguy BeginInvoke often will be simpler, because it is fire and forget, won't hang the control. See https://stackoverflow.com/questions/229554/whats-the-difference-between-invoke-and-begininvoke/229558#229558 – Do-do-new Feb 05 '20 at 16:13
65

I don't know if this is good enough but I made a static ThreadHelperClass class and implemented it as following .Now I can easily set text property of various controls without much coding .

public static class ThreadHelperClass
{
    delegate void SetTextCallback(Form f, Control ctrl, string text);
    /// <summary>
    /// Set text property of various controls
    /// </summary>
    /// <param name="form">The calling form</param>
    /// <param name="ctrl"></param>
    /// <param name="text"></param>
    public static void SetText(Form form, Control ctrl, string text)
    {
        // InvokeRequired required compares the thread ID of the 
        // calling thread to the thread ID of the creating thread. 
        // If these threads are different, it returns true. 
        if (ctrl.InvokeRequired)
        {
            SetTextCallback d = new SetTextCallback(SetText);
            form.Invoke(d, new object[] { form, ctrl, text });
        }
        else
        {
            ctrl.Text = text;
        }
    }
}

Using the code:

 private void btnTestThread_Click(object sender, EventArgs e)
 {
    Thread demoThread =
       new Thread(new ThreadStart(this.ThreadProcSafe));
            demoThread.Start();
 }

 // This method is executed on the worker thread and makes 
 // a thread-safe call on the TextBox control. 
 private void ThreadProcSafe()
 {
     ThreadHelperClass.SetText(this, textBox1, "This text was set safely.");
     ThreadHelperClass.SetText(this, textBox2, "another text was set safely.");
 }
Lee Taylor
  • 7,761
  • 16
  • 33
  • 49
Thunder
  • 10,366
  • 25
  • 84
  • 114
  • 6
    `That's awesome solution`, the most awesome thing is `"it's open for extension and generic"`. you can simply add new UI update functions as you wish, thank you – Basheer AL-MOMANI Jun 29 '16 at 11:15
  • 2
    Great stuff! And if you instead need to read a text: delegate string GetTextCallback(Form f, Control ctrl); public static string GetText(Form form, Control ctrl) { string text; if (ctrl.InvokeRequired) { GetTextCallback d = new GetTextCallback(GetText); text = (string) (form.Invoke(d, new object[] { form, ctrl})); } else { text = ctrl.Text; } return text; } } – hypers Nov 10 '16 at 10:51
  • Can anyone give me an explanation of how i'm gonna to use my Custom text on ThreadProcSafe ? I also tried Eliseo's suggestion but it didn't worked. – Pablo Costa Nov 16 '16 at 11:06
  • I got this working, but I changed it from an onClick event to just a call from another method, renaming it from btnTestThread_Click to just ThreadHelperCall(). Wondering how I can send my controls and values to it so it ultimately passes to ThredProcSafe. I have various conditions that I'd like to use this for but don't want to have to manually create a separate method for each one...ultimately I want to call this process and have ThreadHelperClass.SetText(this, myControl, myValue) where I pass in the control for myControl and the value for myValue. This is all relatively new to me so sorry! – svenGUTT Oct 06 '20 at 15:14
48

you can simply do this.

TextBox.CheckForIllegalCrossThreadCalls = false;
HforHisham
  • 1,914
  • 1
  • 22
  • 34
29

Use the following extensions and just pass the action like:

_frmx.PerformSafely(() => _frmx.Show());
_frmx.PerformSafely(() => _frmx.Location = new Point(x,y));

Extension class:

public static class CrossThreadExtensions
{
    public static void PerformSafely(this Control target, Action action)
    {
        if (target.InvokeRequired)
        {
            target.Invoke(action);
        }
        else
        {
            action();
        }
    }

    public static void PerformSafely<T1>(this Control target, Action<T1> action,T1 parameter)
    {
        if (target.InvokeRequired)
        {
            target.Invoke(action, parameter);
        }
        else
        {
            action(parameter);
        }
    }

    public static void PerformSafely<T1,T2>(this Control target, Action<T1,T2> action, T1 p1,T2 p2)
    {
        if (target.InvokeRequired)
        {
            target.Invoke(action, p1,p2);
        }
        else
        {
            action(p1,p2);
        }
    }
}
RickardN
  • 548
  • 4
  • 11
Mimere
  • 763
  • 2
  • 10
  • 15
  • This worked great! One caveat, I replaced the target.Invoke calls with target.BeginInvoke. I was having some issues with tasks hanging and this solved one of them. – Edyn Jan 11 '16 at 18:27
  • thanks dude, its very good – PurTahan Apr 14 '23 at 11:13
20

Along the same lines as previous answers, but a very short addition that Allows to use all Control properties without having cross thread invokation exception.

Helper Method

    /// <summary>
    /// Helper method to determin if invoke required, if so will rerun method on correct thread.
    /// if not do nothing.
    /// </summary>
    /// <param name="c">Control that might require invoking</param>
    /// <param name="a">action to preform on control thread if so.</param>
    /// <returns>true if invoke required</returns>
    public bool ControlInvokeRequired(Control c,Action a)
    {
        if (c.InvokeRequired) c.Invoke(new MethodInvoker(delegate { a(); }));
        else return false;

        return true;
    }

Sample Usage

    // usage on textbox
    public void UpdateTextBox1(String text)
    {
        //Check if invoke requied if so return - as i will be recalled in correct thread
        if (ControlInvokeRequired(textBox1, () => UpdateTextBox1(text))) return;
        textBox1.Text = ellapsed;
    }

    //Or any control
    public void UpdateControl(Color c,String s)
    {
        //Check if invoke requied if so return - as i will be recalled in correct thread
        if (ControlInvokeRequired(myControl, () => UpdateControl(c,s))) return;
        myControl.Text = s;
        myControl.BackColor = c;
    }
Mike
  • 1,122
  • 2
  • 13
  • 25
  • I must added `()` to `delegate` in `Helper Method`, otherwise, the same Cross-thread error occurred. Thanks, it works. +1 – Vali Maties Mar 27 '20 at 14:50
7

Use a shared container to transfer data between threads.

rotator
  • 294
  • 3
  • 7