Invalid Cross Thread Operations -- How To run class code in a thread and write to the form
If you've ever worked with windows forms, one of the most annoying things that you can ever do is write some massive process and then push go, only to 'freeze' your form in place or lock it on the screen. If nothing else, it will turn completely white and you can't see anything!
The solution to this problem, of course, is to run your code in a thread. This allows you to continue working with the form as the code runs asynchronously in the background. But what happens when you need your thread to alter information on the form, such as change the value of a control on the form for display to the user? You get a nice friendly error message that says:
To illustrate, I've built a little project I've dubbed as ThreadSmart. It's a simple windows application which contains a main form and a class. The form is just called frmMain and the class is ActionClass. In reality, the class and form could be anything that you would need for a real purpose. In this demo, I'm just going to show the interaction between the class and the form.
To start, I've dropped a couple of objects on the form. The texts are txtCurrentState and txtMessages. The progressbar is pb1, and the button is btnRunActionClass.

To create a way to communicate between our class and our form, let's add a delegate for messaging which sends a message and a value for updating the main form with a message box and a value for the progress bar. I will blog more about using delegates later but for now I'll just put this one in the program.cs class inside the namespace, making it global for the entire project. Here is my program.cs file:
The action class is pretty basic for the purpose of this demo. It has one event which is fired to subcribers from the one main method that could be called. In the main method, I've just looped for a count of 100 and then written a random message for every loop. I sleep in the loop to make it appear to take longer. Here is the ActionClass.cs file:
Before launching with a thread, you would write the basic code to subcribe to and handle the event and just update the progress bar and message text using the delegated reporting event. Here is what the frmMain code looks like before we start using the thread:
Here we realize the need for a thread, so add it in:
First, add the threading directive:
Next, change the object to be modal instead of local to the button press event:
Now when the program is run, the threading error occurs:

So this is where the magic needs to happen. To fix it, I need to create local delegates for the event to fire, so that the code will be invokable by the thread. Since there are three controls ultimately being affected in this demo, I will create three events:
And then write the functions to handle the updates when invoked by a caller (I put mine under the original handler function ThreadSmartReportProgress:
Finally, alter the original code in the ThreadSmartReportProgress Function to handle the invoking of the new delegates by the thread:
To make the final project nice and pretty, add the code to set displays on startup and also the code to fire the event from the thread when it is complete. Beware, I did not code here for the fact that you could press the button over and over again, if you want to worry about that, go ahead and make your program thread-safe as well as thread-smart.
After all is said and done, here is the main form code in frmMain.cs:
I hope this will help someone. I know that it will be nice for me to be able to refer to this again in the future. Feel free to comment if there are errors or things you think I could have done better.
The solution to this problem, of course, is to run your code in a thread. This allows you to continue working with the form as the code runs asynchronously in the background. But what happens when you need your thread to alter information on the form, such as change the value of a control on the form for display to the user? You get a nice friendly error message that says:
Cross-thread operation not valid: Control 'blahblahblah' accessed from a thread other than the thread it was created on.
To illustrate, I've built a little project I've dubbed as ThreadSmart. It's a simple windows application which contains a main form and a class. The form is just called frmMain and the class is ActionClass. In reality, the class and form could be anything that you would need for a real purpose. In this demo, I'm just going to show the interaction between the class and the form.
To start, I've dropped a couple of objects on the form. The texts are txtCurrentState and txtMessages. The progressbar is pb1, and the button is btnRunActionClass.
To create a way to communicate between our class and our form, let's add a delegate for messaging which sends a message and a value for updating the main form with a message box and a value for the progress bar. I will blog more about using delegates later but for now I'll just put this one in the program.cs class inside the namespace, making it global for the entire project. Here is my program.cs file:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace ThreadSmart
{
//Public global delegate called Progress, can be called from any class within the namespace:
public delegate void Progress(double progressValue, string progressMessage);
static class Program
{
///
/// The main entry point for the application.
///
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new frmMain());
}
}
}
The action class is pretty basic for the purpose of this demo. It has one event which is fired to subcribers from the one main method that could be called. In the main method, I've just looped for a count of 100 and then written a random message for every loop. I sleep in the loop to make it appear to take longer. Here is the ActionClass.cs file:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ThreadSmart
{
class ActionClass
{
//create the delegated event:
public event Progress ActionProgress;
///
/// Default Constructor:
///
public ActionClass()
{
//TODO: Write constructor code here
}
///
/// Main Action simulates a public method from the class
/// which is a long-running process.
///
public void MainAction()
{
//simulate a long-running process:
for (int i = 0; i < 100; i++)
{
//sleep for a bit...
System.Threading.Thread.Sleep(10000);
//report progress...
ReportProgress(i, String.Format("Some Important Message: {0}", myRandomString(12)));
}
return;
}
//random string function to create a random string for the progress report
//(modified from c-sharp corner http://www.c-sharpcorner.com/UploadFile/mahesh/RandomNumber11232005010428AM/RandomNumber.aspx ):
private string myRandomString(int size)
{
StringBuilder sb = new StringBuilder();
Random rndm = new Random();
char ch;
for (int i = 0; i < size; i++)
{
ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * rndm.NextDouble() + 65)));
sb.Append(ch);
}
return sb.ToString();
}
//create the event handler in the class to fire the event:
private void ReportProgress(double value, string message)
{
if (ActionProgress != null)
{
//fire the event if there is a subscriber:
ActionProgress(value, message);
}
}
}
}
Before launching with a thread, you would write the basic code to subcribe to and handle the event and just update the progress bar and message text using the delegated reporting event. Here is what the frmMain code looks like before we start using the thread:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace ThreadSmart
{
public partial class frmMain : Form
{
public frmMain()
{
InitializeComponent();
}
///
/// Run the long running process...
///
private void btnRunActionClass_Click(object sender, EventArgs e)
{
//create a new action class object:
ActionClass myAC = new ActionClass();
//add the handler for the actionclass progress event:
myAC.ActionProgress += new Progress(ThreadSmartReportProgress);
//run the long-running process:
myAC.MainAction();
}
///
/// Handle the report event from the class:
///
private void ThreadSmartReportProgress(double value, string message)
{
//set the message
txtMessages.Text = message;
//set the progressbar
if (value <= 100)
{
pb1.Value = (int)value;
}
else
{
pb1.Value = (int)(value % 100);
}
//refresh the form:
this.Refresh();
}
}
}
Here we realize the need for a thread, so add it in:
First, add the threading directive:
using System.Threading;
Next, change the object to be modal instead of local to the button press event:
public partial class frmMain : Form
{
//modal declaration of the action class for use in the form:
ActionClass m_myAC;
...
///
/// Run the long running process...
///
private void btnRunActionClass_Click(object sender, EventArgs e)
{
//create a new instance of the action class object:
m_myAC = new ActionClass();
//add the handler for the actionclass progress event:
m_myAC.ActionProgress += new Progress(ThreadSmartReportProgress);
//Create a thread to launch the class in
Thread ACThread = new Thread(new ThreadStart(DoMainAction));
//launch the process to convert the file in a thread:
ACThread.Start();
}
///
/// Run the action in a thread
///
private void DoMainAction()
{
//run the long-running process:
m_myAC.MainAction();
}
//...
}
Now when the program is run, the threading error occurs:
So this is where the magic needs to happen. To fix it, I need to create local delegates for the event to fire, so that the code will be invokable by the thread. Since there are three controls ultimately being affected in this demo, I will create three events:
public partial class frmMain : Form
{
//modal declaration of the action class for use in the form:
ActionClass m_myAC;
//local delegates to set local controls from a caller:
delegate void SetPBValue(double pbValue);
delegate void SetTXTProgressMessage(string pMessage);
delegate void SetTXTCurrentState(string message);
//...
}
And then write the functions to handle the updates when invoked by a caller (I put mine under the original handler function ThreadSmartReportProgress:
//HERE are the local instance functions which will actually set the values
//they are INVOKED in the event capture from the class delegated raised event:
private void SetPBValueCallback(double value)
{
pb1.Value = (int)value;
}
private void SetTextValueCallback(string message)
{
txtMessages.Text = message;
}
private void SetStateTextValueCallback(string message)
{
txtCurrentState.Text = message;
}
Finally, alter the original code in the ThreadSmartReportProgress Function to handle the invoking of the new delegates by the thread:
///
/// Handle the report event from the class:
///
private void ThreadSmartReportProgress(double value, string message)
{
//since the progressbar is local to the form, it is UNSAFE to let an external thread
//modify it. For that reason -- have to use the InvokeRequired attribute to handle
//the modification of the thread:
if (this.pb1.InvokeRequired)
{
//create a new instance of the delegate, point to the local function to set the value:
SetPBValue pbv = new SetPBValue(SetPBValueCallback);
//make sure pb value doesn't go over the value of 100:
if (value > 100)
{
value = value % 100;
}
//invoke the delegate pointing at the local function with the passed on value:
this.Invoke(pbv, new object[] { value });
}
//handle textbox same as for PB1
if (this.txtMessages.InvokeRequired)
{
//same as for PB1
SetTXTProgressMessage txtpm = new SetTXTProgressMessage(SetTextValueCallback);
this.Invoke(txtpm, new object[] { message });
}
}
To make the final project nice and pretty, add the code to set displays on startup and also the code to fire the event from the thread when it is complete. Beware, I did not code here for the fact that you could press the button over and over again, if you want to worry about that, go ahead and make your program thread-safe as well as thread-smart.
After all is said and done, here is the main form code in frmMain.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace ThreadSmart
{
public partial class frmMain : Form
{
//modal declaration of the action class for use in the form:
ActionClass m_myAC;
//local delegates to set local controls from a caller:
delegate void SetPBValue(double pbValue);
delegate void SetTXTProgressMessage(string pMessage);
delegate void SetTXTCurrentState(string message);
public frmMain()
{
InitializeComponent();
}
private void frmMain_Load(object sender, EventArgs e)
{
txtCurrentState.Text = "Waiting for action...";
}
///
/// Run the long running process...
///
private void btnRunActionClass_Click(object sender, EventArgs e)
{
//create a new instance of the action class object:
m_myAC = new ActionClass();
//add the handler for the actionclass progress event:
m_myAC.ActionProgress += new Progress(ThreadSmartReportProgress);
//Create a thread to launch the class in
Thread ACThread = new Thread(new ThreadStart(DoMainAction));
//reset the displays
txtCurrentState.Text = "Running ...";
pb1.Value = 0;
//launch the process to convert the file in a thread:
ACThread.Start();
}
///
/// Run the action in a thread
///
private void DoMainAction()
{
//run the long-running process:
m_myAC.MainAction();
if (this.txtCurrentState.InvokeRequired)
{
//same as for PB1/txtMessages
SetTXTCurrentState txtState = new SetTXTCurrentState(SetStateTextValueCallback);
this.Invoke(txtState, new object[] { "Thread Completed!" });
SetPBValue pbv = new SetPBValue(SetPBValueCallback);
this.Invoke(pbv, new object[] { 100 });
}
}
///
/// Handle the report event from the class:
///
private void ThreadSmartReportProgress(double value, string message)
{
//since the progressbar is local to the form, it is UNSAFE to let an external thread
//modify it. For that reason -- have to use the InvokeRequired attribute to handle
//the modification of the thread:
if (this.pb1.InvokeRequired)
{
//create a new instance of the delegate, point to the local function to set the value:
SetPBValue pbv = new SetPBValue(SetPBValueCallback);
//make sure pb value doesn't go over the value of 100:
if (value > 100)
{
value = value % 100;
}
//invoke the delegate pointing at the local function with the passed on value:
this.Invoke(pbv, new object[] { value });
}
//handle textbox same as for PB1
if (this.txtMessages.InvokeRequired)
{
//same as for PB1
SetTXTProgressMessage txtpm = new SetTXTProgressMessage(SetTextValueCallback);
this.Invoke(txtpm, new object[] { message });
}
}
//HERE are the local instance functions which will actually set the values
//they are INVOKED in the event capture from the class delegated raised event:
private void SetPBValueCallback(double value)
{
pb1.Value = (int)value;
}
private void SetTextValueCallback(string message)
{
txtMessages.Text = message;
}
private void SetStateTextValueCallback(string message)
{
txtCurrentState.Text = message;
}
}
}
I hope this will help someone. I know that it will be nice for me to be able to refer to this again in the future. Feel free to comment if there are errors or things you think I could have done better.
Comments
Post a Comment