Accessing controls from multiple threads causes exceptions; Control.InvokeRequired and Control.Invoke can solve the problem. category 'KB', language C# and VB.NET, created 24-Jul-2009, version V1.5 (26-Nov-2009), by Luc Pattyn |
License: The author hereby grants you a worldwide, non-exclusive license to use and redistribute the files and the source code in the article in any way you see fit, provided you keep the copyright notice in place; when code modifications are applied, the notice must reflect that. The author retains copyright to the article, you may not republish or otherwise make available the article, in whole or in part, without the prior written consent of the author. Disclaimer: This work is provided “as is”, without any express or implied warranties or conditions or guarantees. You, the user, assume all risk in its use. In no event will the author be liable to you on any legal theory for any special, incidental, consequential, punitive or exemplary damages arising out of this license or the use of the work or otherwise. |
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 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.
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.
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:
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.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.Control.EndInvoke()
is used to wait for termination of a BeginInvoke, and retrieves the return
value if any.Control.CreateGraphics()
is not very relevant here.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 the Invoke
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:
// 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
}
}
' 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.
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:
// 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
}
' 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.
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:
// 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
}
}
' 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
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:
// 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
}
}
}
' 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().
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:
// 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!
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:
Tick
handler of a System.Windows.Forms.Timer
,
which therefore often is the right timer for GUI actions;ProgressChanged
and RunWorkerCompleted
handlers
(but not the DoWork
event) of a System.ComponentModel.BackgroundWorker
, provided it was created
by the GUI thread.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:
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
}
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
, ...
More on the subject is available everywhere; this article by Senthil Kumar dives rather deep in the matter.
Perceler |
Copyright © 2012, Luc Pattyn |
Last Modified 02-Sep-2013 |