Cross-thread operation not valid
As I’ve been doing a lot of programming with win-forms, c# and c++ and making my applications multi-threaded - I came across a problem of allowing a newly created thread change the UI. Below I will discuss how to do this in c#.
The error I came across was the following:
Exception
Cross-thread operation not valid: Control ‘control_name’ accessed from a thread other than the thread it was created on.
Because I created my own background thread to carry out a process and then display the result back to the user in the UI win-form control (an example of a control is a list-view, text-box, button and so on…), an exception was raised as stated above. The main thread in a win-forms application is the parent of the control and not a newly created background thread.
Further research into the matter showed me that you need to make sure that calls to a control on the UI are done in a thread-safe way. Not doing so in a safe way creates the exception.
According to MSDN:
Access to Windows Forms controls is not inherently thread safe. If you have two or more threads manipulating the state of a control, it is possible to force the control into an inconsistent state. Other thread-related bugs are possible, such as race conditions and deadlocks. It is important to make sure that access to your controls is performed in a thread-safe way.
Basically to make thread safe calls you basically need to use the Invoke method.
Invoke(delegate) - Executes the specified delegate on the parent thread that owns the controls underlying window handle. Thus we are allowed to create our own threads and manipulate the UI control (set the value of a textbox, populate a list-view….) in a thread safe way.
Scenario: unsafe thread call
For example if we had a button and in the Click event handler code for that button we created a new thread:
private void button1_click(object sender, EventArgs e)
{
textBox1.Text = "Set text by main thread.";
Thread t_thread1 = new Thread(new ThreadStart(setText));
t_thread1.Start();
}
private void setText()
{
textBox1.Text = "Set text by background thread.";
}
The above code will make the Cross-thread operation not valid exception to fire, since we did not call the Invoke method to change the text property of the TextBox control from our own background thread t_thread1.
Scenario: safe thread call
To make thread safe calls the following need to be taken into account:
Check for the InvokeRequired property of the control - gets a boolean value indicating whether the caller must call an invoke method since the caller is on a different thread than the one that was created on.
If InvokeRequired returns true = call Invoke method with delegate that make the actual call to the control.
Else if InvokeRequired returns false = access the control directly since the main thread is accessing the control UI and not a background thread.
The Invoke method requires a delegate as a parameter:
Invoke(delegate)
plus optional object to send as an parameter to the calling method.
Invoke(delegate,Object);
This delegate will make the actual call to the control. As usual this delegate (a reference to a method), needs to have the same signature as the method which it is referencing.
The example below shows how to make a thread safe call and successfully set the text of the textBox from a background thread.
private void button1_click(object sender, EventArgs e)
{
Thread t_thread1 = new Thread(new ThreadStart(ThreadSafeCall));
t_thread1.Start();
}
delegate void setTextCallback(string text);
private void setText(string text)
{
textBox1.Text = text;
}
private void ThreadSafeCall()
{
if(this.textBox1.InvokeRequired)
{
setTextCallback set_text_delegate = new setTextCallback(setText);
this.Invoke(set_text_delegate,new object[]{
"text set by background thread."
});
}
else
{
setText("text set by main thread.");
}
}
I hope the above comes in handy whenever you need to access a control with a background thread.