140

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!


  • You are trying to modify the UI (main thread) via another thread. - Evan Mulawski

6 답변


254

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


  • 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
  • will control.BeginInvoke work too? the solution can be 1 line too like this, right? textbox1.BeginInvoke((MethodInvoker)delegate(){ textbox1.Text = txt.ToString(); }); - newbieguy
  • 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

42

I don't know if this is good enough but I did 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.");
        }


  • 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
  • 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
  • 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

24

you can simply do this.

TextBox.CheckForIllegalCrossThreadCalls = false;


  • Bad idea as the error will come back when you do a 'release' compile. - omglolbah
  • But good idea for training the multithreaded programming - Mehdi Khademloo
  • I've got some old sample code that apparently worked in the days of .Net 2. I just need to analyze other aspects of the code, so this answer is great for what I'm doing! - Dave
  • @DerfSkren Care to give further explanations?^ - Eric Wu
  • @EricWu setting that flag has the same effect whether you do debug or release builds. The "problem" of being warned about something that is actually safe and hence being forced to reinvent the wheel on every GUI you ever create won't come back. - Derf Skren

20

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


  • 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

10

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


6

Use a shared container to transfer data between threads.

Linked


Related

Latest