[转]Article: Invalid cross-thread operations
原文:http://www.perceler.com/articles1.php?art=crossthreads1
![]() |
Article: Invalid cross-thread operations |
When a WinForms application uses some threads, the ThreadPool, a BackgroundWorker, it may inadvertently get an InvalidOperationException
mentioning some Cross-Thread violation ("Cross-thread operation not valid: Control '...' accessed from a thread other than the thread it was created on."). This article explains what is going on, and how the situation should be dealt with.
The problem
The WinForms GUI (Graphical User Interface) consists of many Controls to render forms, buttons, text boxes, etc. showing the state of an app, and allowing the user to interact with it. For performance reasons, those Controls are not thread-safe, i.e. when several threads were to access a single Control at the same time, unpredictable behavior might result, including partially unpainted controls, a freezing GUI, a crashing app; all these phenomena may occur right away, or they may happen after several minutes or hours of operation, as threading effects are stochastic, they depend on a lot of factors.
One of the primal rules in Windows is: a Control should be touched only by the thread that created it; so other threads should not call its methods, nor read or write its properties. It isn't obvious how to achieve that under all situations.
Matters get worse when you realize most if not all Controls are linked to each other, as they are located on one or several Forms, and Forms have relationships too, with Owner, Parent and z-order. So the net result is a huge amount of data, all interconnected, and all without thread-safety. The practical guide line therefore is:create and manipulate your controls from the main thread only; so the main thread (aka GUI thread) should be the only one creating Controls, calling their methods, getting and setting their properties, etc.
Not the solution
Older versions (1.0 and 1.1) of .NET did not do anything about the problem. If your app did something wrong, you would suffer the consequences.
.NET 2.0 introduced a detection mechanism: as soon as a thread wrongly touches a Control, an InvalidOperationException
gets thrown. That does not solve the problem, however it eases the detection of possible problems, and reduces the likelihood of illegal cross-thread bugs showing up after deployment.
.NET 2.0 also introduced a property Control.CheckForIllegalCrossThreadCalls
which is true by default; setting it false returns the app to the old 1.x situation where problems exist and may go undetected for a long time. It does not solve any problem, so don't use it. Not ever.
The solution
Every Control has one property and a few methods that are thread-safe, so you are allowed to call them from whatever thread you choose; they are:
- Method
Control.Invoke(delegate, object[])
causes synchronous execution of the delegate on the right thread, and passes the optional parameters; it waits for termination of the delegate. - Method
Control.BeginInvoke(delegate, object[])
causes asynchronous execution of the delegate on the right thread, and passes the optional parameters; it does not wait for termination of the delegate. - Method
Control.EndInvoke()
is used to wait for termination of a BeginInvoke, and retrieves the return value if any. - Method
Control.CreateGraphics()
is not very relevant here. - The
Control.InvokeRequired
property is read-only; when it returns false, it is safe to access the Control from the current thread; when true, it needs one of theInvoke
methods to touch the Control.
Based on the above property and methods, one can devise a pattern that allows any thread to execute a method that touches a Control. Here is the canonical form of this pattern:
![](http://www.perceler.com/images/minus9.gif)
// the canonical form (C# consumer) public delegate void ControlStringConsumer(Control control, string text); // defines a delegate type public void SetText(Control control, string text) { if (control.InvokeRequired) { control.Invoke(new ControlStringConsumer(SetText), new object[]{control, text}); // invoking itself } else { control.Text=text; // the "functional part", executing only on the main thread } }
![](http://www.perceler.com/images/minus9.gif)
' the canonical form (VB.NET consumer) Private Delegate Sub ControlStringConsumer(ByVal control As Control, ByVal text As String) ' defines a delegate type Private Sub SetText(ByVal control As Control, ByVal text As String) If control.InvokeRequired Then control.Invoke(New ControlStringConsumer(AddressOf SetText), New Object() {control, text}) ' invoking itself Else control.Text = text ' the "functional part", executing only on the main thread End If End Sub
What happens is this: the caller sees InvokeRequired returning "true", hence control.Invoke gets executed, causing the same method being executed again, now however on the right thread. This time around, InvokeRequired returns "false" hence the functional part of the method gets executed, setting the Text property to the desired value.
Warning: there really should be no code outside the if-else construct, since such code would execute twice.
Alternative forms of the same solution
We strongly recommend sticking to the canonical form, and stuffing whatever needs to be done inside the else
block of the method. However, for completeness we mention an alternative that yields the same outcome:
![](http://www.perceler.com/images/minus9.gif)
// the riskier form (C# consumer) public delegate void ControlStringConsumer(Control control, string text); // defines a delegate type public void SetText(Control control, string text) { control.Invoke(new ControlStringConsumer(SetTextGUI), new object[]{control, text}); // invoking another method } public void SetTextGUI(Control control, string text) { control.Text=text; // the "functional part", acceptable only on the main thread }
![](http://www.perceler.com/images/minus9.gif)
' the riskier form (VB.NET consumer) Private Delegate Sub ControlStringConsumer(ByVal control As Control, ByVal text As String) ' defines a delegate type Private Sub SetText(ByVal control As Control, ByVal text As String) control.Invoke(New ControlStringConsumer(AddressOf SetTextGUI), New Object() {control, text}) ' invoking another method End Sub Private Sub SetTextGUI(ByVal control As Control, ByVal text As String) control.Text = text ' the "functional part", acceptable only on the main thread End Sub
In the above form, we don't check InvokeRequired, we just assume we are on the wrong thread and always call Invoke. If some thread would directly call SetTextGUI, an illegal cross-thread access would occur, which .NET 2.0 and above would honor with an InvalidOperationException
. The canonical form is much safer as there is only one method, and anyone can call it safely.
Retrieving information from a Control
As Control.Invoke works synchronously and is capable of returning an object, one can use the same scheme to fetch data from a Control. The example shows how to get the Text property of any kind of Control:
![](http://www.perceler.com/images/minus9.gif)
// the canonical form (C# producer) public delegate void ControlStringProducer(Control control); // defines a delegate type public string GetText(Control control) { if (control.InvokeRequired) { return (string)control.Invoke(new ControlStringProducer(GetText), new object[]{control}); // invoking itself } else { return control.Text; // the "functional part", executing only on the main thread } }
![](http://www.perceler.com/images/minus9.gif)
' the canonical form (VB.NET producer) Private Delegate Function ControlStringProducer(ByVal control As Control) As String ' defines a delegate type Private Function GetText(ByVal control As Control, ByVal text As String) As String If control.InvokeRequired Then return control.Invoke(New ControlStringProducer(AddressOf GetText), New Object() {control}) As String ' invoking itself Else return control.Text ' the "functional part", executing only on the main thread End If End Function
Performance optimizations
Obviously all the above takes a bit of a performance hit: the current thread has to yield for the main thread, which executes the delegate, then the original thread can continue. And each invocation creates a new delegate, and a new array of parameters. Two obvious ways to save some of the work, is by creating the delegate only once and keeping it around; and by keeping the number of parameters to the minimum. One possibility is by creating separate methods for different controls.
One must be extremely careful when lots of operations need to be performed on the GUI; e.g. when a thread fills a list of strings, and those need to be added to a ListBox, rather than having the thread loop the list and invoke a ListBox.Add for each individual item, it would be much more efficient to pass the list as a parameter in the delegate, and have one invoke where the else
block adds all the items at once. Like so:
![](http://www.perceler.com/images/minus9.gif)
// the massive canonical form (C# consumer) public delegate void ListBoxStringsConsumer(ListBox listbox, string[] texts); // defines a delegate type public void SetTexts(ListBox listbox, string[] texts) { if (listbox.InvokeRequired) { listbox.Invoke(new ListBoxStringsConsumer(SetTexts), new object[]{listbox, texts}); // invoking itself } else { foreach(string text in texts) { listbox.Items.Add(text); // this statement executing only on the main thread } } }
![](http://www.perceler.com/images/minus9.gif)
' the massive canonical form (VB.NET consumer) Private Delegate Sub ListBoxStringsConsumer(ByVal listbox As ListBox, ByVal texts() As String) ' defines a delegate type Private Sub SetTexts(ByVal listbox As ListBox, ByVal texts() As String) If listbox.InvokeRequired Then listbox.Invoke(New ListBoxStringsConsumer(AddressOf SetTexts), New Object() {listbox, texts}) ' invoking itself Else For Each text As String In texts listbox.Items.Add(text) ' the "functional part", executing only on the main thread Next End If End Sub
Obviously, if the control has a method that supports multiple inserts/additions, as with ListBox.Items.AddRange()
then such method should be used instead.
Especially for progress bars which typically get updated at high speed in a loop, one could save on thread switches by making sure the new progress value differs from the previous one before executing Invoke
; obviously this also applies when using BackgroundWorker.ReportProgress().
Code optimizations
When using specialized methods, that operate on a specific control, one often only needs a single parameter. That is where Action<T>
comes in handy, as this delegate is predefined and takes one parameter of any type T
you choose. In the rare situation where no parameter is required at all, MethodInvoker
would be appropriate.
This article shows a way to hide the InvokeRequired
stuff in a small class; I'm not really fond of that idea as it doesn't add much value in my opinion.
Since .NET 2.0 C# also supports anonymous methods and delegates, so we now can write the solution in a more compact way:
![](http://www.perceler.com/images/minus9.gif)
// the anonymous form (C# consumer) public void SetText(Control control, string text) { control.Invoke(new MethodInvoker(delegate() { control.Text=text; })); }
The trick here is the anonymous delegate inherits the scope of the surrounding code block, hence it has direct access to control
and text
.
By the way, the more logical control.Invoke(delegate {control.Text=text;});
does not compile!
Where to use
As controls should only be operated upon from the main thread, one should use the InvokeRequired/Invoke pattern everytime one isn't sure about the identity of the executing thread. Obviously control events get fired on the main thread, so a button click handler can safely access a Label; however almost all asynchronous handlers, timers, serial ports, networking, etc. should be suspect, as would real and hidden threads, such as ThreadPool threads and BackgroundWorkers.
There are only a few exceptions, they are clearly documented; the most important ones are:
- the
Tick
handler of aSystem.Windows.Forms.Timer
, which therefore often is the right timer for GUI actions; - the
ProgressChanged
andRunWorkerCompleted
handlers (but not theDoWork
event) of aSystem.ComponentModel.BackgroundWorker
, provided it was created by the GUI thread.
Appendix 1: BackgroundWorker
The BackgroundWorker class helps in avoiding InvalidOperationExceptions as it has two events that fire on the GUI thread (actually on the thread that created the BackgroundWorker, which normally is the GUI thread). So if you need extra threads for handling some calculations or communications in the background, away from the GUI thread, and those operations need to access the GUI, it is worthwhile considering a BackgroundWorker.
There are some disadvantages as you will have less control over the thread's behavior (you can't alter its priority, can't abort it, etc), however it allows for much easier GUI access, as you don't need the InvokeRequired/Invoke pattern as set forth earlier. You can set new values to Controls from inside the ProgressChanged
and RunWorkerCompleted
handlers. The ReportProgress
method has two overloads, one takes an integer, the other an integer and an object; these parameters get passed on to the ProgressChanged
handler. Note that the integer value "ProgressPercentage" can be any valid int value, so it is not restricted to the range [0, 100].
Typical use could be:
- a progress percentage; a single integer, when that is all the GUI access ever needed.
- an integer code specifying an operation, and an object (often a string) passing a new value
![](http://www.perceler.com/images/minus9.gif)
void bgw1_DoWork(object sender, DoWorkEventArgs e) { label1.Text="invalid access"; // No, this is not allowed! ReportProgress(0, "start"); for (int progress=0; progress<=100; progress+=10) { ... ReportProgress(1, progress); } } void bgw1_ProgressChanged(object sender, ProgressChangedEventArgs e) { int code=e.ProgressPercentage; object state=e.UserState; switch (code) { case 0: label1.Text=(string)state; // this is fine, as the event fires on the thread that created the BGW break; case 1: progressbar1.Value=(int)state; // this is fine, as the event fires on the thread that created the BGW break; } } void bgw1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { label1.Text="done"; // this is fine, as the event fires on the thread that created the BGW }
Appendix 2: Non-Control GUI parts
There are a couple of GUI parts that do not inherit from Control, and hence don't offer InvokeRequired
and Invoke()
. Examples include ToolStripMenuItem
,ToolStripSeparator
, ToolStripTextBox
, ToolStripComboBox
. As far as I know, these GUI parts need similar precautions as regular Controls, although they don't throw InvalidOperationExceptions. Lacking the InvokeRequired property and Invoke() method, my best advice is to use these properties and methods on the containing Control, which could be a MenuStrip
, a ContextMenuStrip
, a ToolStrip
, ...
Perceler (Company # 0895.737.095) |
Copyright © 2012, Luc Pattyn |
Last Modified 02-Sep-2013 |