This error is indicated by the following exception message: "Cross-thread operation not valid: Control 'AnyControl' accessed from a thread other than the thread it was created on."
It mostly occurs in early progress of legacy code migration from .Net 1.1 to 2.0 or higher, but those not familiar with the pervert conjunction between multi-threading and ActiveX objects can also easily encounter this error message.
ActiveX objects only work in STA containers, and may only be used by the thread, that handles the containers message queue, because they dont have any thread sync methods. You can have multiple threads in an STA application context, but beauty of synchronizing them lies in your hands.
Yes, as most problems, this also has multiple solutions. Let's start with the worst, and progress towards something I call satisfying.
Usually the legacy migrator sits by the code, and thinks: "This is not my code after all, let's find the fastest solution, and get rid of the problem..." Hell yeah, for them, Microsoft left a switch to do that, the following line of code at the entry point of the application will get rid of these messages:
Control.CheckForIllegalCrossThreadCalls = FalseOuch. A rookie knows, that a long property or class name cant mean any good in the .Net framework (like DataGridViewAutoSizeColumnsModeEventArgs) But this one works. Though it only tells the compiler to simply skip the check, but who cares? The code runs smooth, thats all...
We have to mention here, that though the code runs fine, it can cause nasty user interface glitches. By a text box and a single working background thread its no problem, but by two threads and a list... Well see it for yourself.
Who does not care much of the given code, and looks the technical side: "Threading? Winforms? Why don't you use a Background Worker? Or Application.DoEvents()?" Well, thats a good answer, but in most cases we don't want to tinker with the logic and rewrite it. Using a background worker is the least elegant way of multi threading after all, and Application.DoEvents() is like GoTo... Every time you use it, god kills a puppy...
Invoking. Invoking is a WinForms implemented way to pass a method delegate cross-thread. If you are interrested in the backend, I'd say get yourself the Lutz Roeder's .Net Reflector (a useful tool for any coder who works with MSIL compiled languages), and take a look at the framework libraries. Lets take a simple example:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Dim workerThread As New Threading.Thread(AddressOf illegalCrossThreadCall) workerThread.IsBackground = True workerThread.Start() End Sub Private Sub illegalCrossThreadCall() Dim tempStorage As Integer tempStorage = 2 ^ 16 'This is the placeholder of a very time-consuming procedure TextBox1.Text = tempStorage.ToString() End Sub
Now this code will throw a nasty exception, when it tries to set the text property of the control. We will make a large step now, and instantly overwrite this with a refactored version, something like this:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Dim workerThread As New Threading.Thread(AddressOf LegalCrossThreadCall) workerThread.IsBackground = True workerThread.Start() End Sub Private Sub LegalCrossThreadCall() Dim tempStorage As Integer tempStorage = 2 ^ 16 'This is still the placeholder of a very time-consuming procedure SetControlText(TextBox1, tempStorage.ToString()) End Sub ''' <summary> ''' This is a delegate we'll use to pass the method call between threads ''' </summary> ''' <param name="control">The control we want to set the text property on</param> ''' <param name="text">The text supplied for the controls text property</param> ''' <remarks>The method parameters should be the same as the base method we pass</remarks> Private Delegate Sub SetControlTextDelegate(ByVal control As Control, ByVal text As String) ''' <summary> ''' This is a general, thread safe function to set the text property ''' of any given control ''' </summary> ''' <param name="control">The control we want to set the text property on</param> ''' <param name="textToSet">The text supplied for the controls text property</param> ''' <remarks></remarks> Private Sub SetControlText(ByVal control As Control, ByVal textToSet As String) 'The InvokeRequired property shows if the calling thread is the same as the 'one responsible for the message queue of the control If control.InvokeRequired Then 'If not, we create a delegate from this method, 'and invoke it on the controls thread Dim d As New SetControlTextDelegate(AddressOf SetControlText) control.Invoke(d, New Object() {control, textToSet}) Else 'If we're on the right thread, set the text control.Text = textToSet End If End Sub
We implemented a new method to set a controls property, now we can call from any thread or function, it will always act the same safe way to set the text. This can be a satisfying solution, a balance of modifying the given legacy code only that much, as it's needed to get safe results.
Comments
Great job! This is by far
Great job! This is by far the most clear tutorial on the web I found in years. And, worked like a charm.
Well written and very
Well written and very helpful!
thanks!
Cheers
Happy that you found use of it.
nice language and best
nice language and best explanation! god kills a puppy, lol