232

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!

1
  • 2
    You are trying to modify the UI (main thread) via another thread. Commented May 27, 2012 at 16:09

6 Answers 6

382

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()); } 
Sign up to request clarification or add additional context in comments.

5 Comments

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. }
will control.BeginInvoke work too? the solution can be 1 line too like this, right? textbox1.BeginInvoke((MethodInvoker)delegate(){ textbox1.Text = txt.ToString(); });
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.
@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.
@newbieguy BeginInvoke often will be simpler, because it is fire and forget, won't hang the control. See stackoverflow.com/questions/229554/…
69

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

4 Comments

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
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; } }
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.
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!
51

you can simply do this.

TextBox.CheckForIllegalCrossThreadCalls = false; 

7 Comments

Bad idea as the error will come back when you do a 'release' compile.
But good idea for training the multithreaded programming
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!
@DerfSkren Care to give further explanations?^
@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.
|
30

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

1 Comment

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.
21

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

2 Comments

I must added () to delegate in Helper Method, otherwise, the same Cross-thread error occurred. Thanks, it works. +1
...............Thanks.................
7

Use a shared container to transfer data between threads.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.