How to: Make Thread-Safe Calls to Windows Forms Controls
If you use multithreading to improve the performance your Windows Forms applications, you must be careful to make calls to your controls in a thread-safe way.
Example
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 as well, including race conditions and deadlocks. It is important to ensure that access to your controls is done in a thread-safe way.
The .NET Framework helps you detect when you are accessing your controls in a manner that is not thread safe. When you are running your application in the debugger, and a thread other than the one which created a control attempts to call that control, the debugger raises an InvalidOperationException with the message, "Control control name accessed from a thread other than the thread it was created on."
This exception occurs reliably during debugging and, under some circumstances, at run time. You are strongly advised to fix this problem when you see it. You might see this exception when you debug applications that you wrote with the .NET Framework prior to .NET Framework version 2.0.
Note |
|---|
| You can disable this exception by setting the value of the CheckForIllegalCrossThreadCalls property to false. This causes your control to run the same way as it would run under Visual Studio 2003. |
The following code example shows how to call Windows Forms controls in a thread-safe manner and not in a thread-safe manner from a worker thread. It shows a way of setting the Text property of a TextBox control in a manner that is not thread safe, and it shows two thread-safe ways of setting the Text property.
using System; using System.ComponentModel; using System.Threading; using System.Windows.Forms; namespace CrossThreadDemo { public class Form1 : Form { // This delegate enables asynchronous calls for setting // the text property on a TextBox control. delegate void SetTextCallback(string text); // This thread is used to demonstrate both thread-safe and // unsafe ways to call a Windows Forms control. private Thread demoThread = null; // This BackgroundWorker is used to demonstrate the // preferred way of performing asynchronous operations. private BackgroundWorker backgroundWorker1; private TextBox textBox1; private Button setTextUnsafeBtn; private Button setTextSafeBtn; private Button setTextBackgroundWorkerBtn; private System.ComponentModel.IContainer components = null; public Form1() { InitializeComponent(); } protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } // This event handler creates a thread that calls a // Windows Forms control in an unsafe way. private void setTextUnsafeBtn_Click( object sender, EventArgs e) { this.demoThread = new Thread(new ThreadStart(this.ThreadProcUnsafe)); this.demoThread.Start(); } // This method is executed on the worker thread and makes // an unsafe call on the TextBox control. private void ThreadProcUnsafe() { this.textBox1.Text = "This text was set unsafely."; } // This event handler creates a thread that calls a // Windows Forms control in a thread-safe way. private void setTextSafeBtn_Click( object sender, EventArgs e) { this.demoThread = new Thread(new ThreadStart(this.ThreadProcSafe)); this.demoThread.Start(); } // This method is executed on the worker thread and makes // a thread-safe call on the TextBox control. private void ThreadProcSafe() { this.SetText("This text was set safely."); } // This method demonstrates a pattern for making thread-safe // calls on a Windows Forms control. // // If the calling thread is different from the thread that // created the TextBox control, this method creates a // SetTextCallback and calls itself asynchronously using the // Invoke method. // // If the calling thread is the same as the thread that created // the TextBox control, the Text property is set directly. private void SetText(string text) { // InvokeRequired required compares the thread ID of the // calling thread to the thread ID of the creating thread. // If these threads are different, it returns true. if (this.textBox1.InvokeRequired) { SetTextCallback d = new SetTextCallback(SetText); this.Invoke(d, new object[] { text }); } else { this.textBox1.Text = text; } } // This event handler starts the form's // BackgroundWorker by calling RunWorkerAsync. // // The Text property of the TextBox control is set // when the BackgroundWorker raises the RunWorkerCompleted // event. private void setTextBackgroundWorkerBtn_Click( object sender, EventArgs e) { this.backgroundWorker1.RunWorkerAsync(); } // This event handler sets the Text property of the TextBox // control. It is called on the thread that created the // TextBox control, so the call is thread-safe. // // BackgroundWorker is the preferred way to perform asynchronous // operations. private void backgroundWorker1_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e) { this.textBox1.Text = "This text was set safely by BackgroundWorker."; } #region Windows Form Designer generated code private void InitializeComponent() { this.textBox1 = new System.Windows.Forms.TextBox(); this.setTextUnsafeBtn = new System.Windows.Forms.Button(); this.setTextSafeBtn = new System.Windows.Forms.Button(); this.setTextBackgroundWorkerBtn = new System.Windows.Forms.Button(); this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker(); this.SuspendLayout(); // // textBox1 // this.textBox1.Location = new System.Drawing.Point(12, 12); this.textBox1.Name = "textBox1"; this.textBox1.Size = new System.Drawing.Size(240, 20); this.textBox1.TabIndex = 0; // // setTextUnsafeBtn // this.setTextUnsafeBtn.Location = new System.Drawing.Point(15, 55); this.setTextUnsafeBtn.Name = "setTextUnsafeBtn"; this.setTextUnsafeBtn.TabIndex = 1; this.setTextUnsafeBtn.Text = "Unsafe Call"; this.setTextUnsafeBtn.Click += new System.EventHandler(this.setTextUnsafeBtn_Click); // // setTextSafeBtn // this.setTextSafeBtn.Location = new System.Drawing.Point(96, 55); this.setTextSafeBtn.Name = "setTextSafeBtn"; this.setTextSafeBtn.TabIndex = 2; this.setTextSafeBtn.Text = "Safe Call"; this.setTextSafeBtn.Click += new System.EventHandler(this.setTextSafeBtn_Click); // // setTextBackgroundWorkerBtn // this.setTextBackgroundWorkerBtn.Location = new System.Drawing.Point(177, 55); this.setTextBackgroundWorkerBtn.Name = "setTextBackgroundWorkerBtn"; this.setTextBackgroundWorkerBtn.TabIndex = 3; this.setTextBackgroundWorkerBtn.Text = "Safe BW Call"; this.setTextBackgroundWorkerBtn.Click += new System.EventHandler(this.setTextBackgroundWorkerBtn_Click); // // backgroundWorker1 // this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted); // // Form1 // this.ClientSize = new System.Drawing.Size(268, 96); this.Controls.Add(this.setTextBackgroundWorkerBtn); this.Controls.Add(this.setTextSafeBtn); this.Controls.Add(this.setTextUnsafeBtn); this.Controls.Add(this.textBox1); this.Name = "Form1"; this.Text = "Form1"; this.ResumeLayout(false); this.PerformLayout(); } #endregion [STAThread] static void Main() { Application.EnableVisualStyles(); Application.Run(new Form1()); } } }
Calls to a Windows Forms Control That Are Not Thread Safe
The way to call a Windows Forms control that is not thread safe is to call directly from a worker thread. When you are debugging your application, the debugger raises an InvalidOperationException to warn you about calls to your controls that are not thread safe.
// This event handler creates a thread that calls a // Windows Forms control in an unsafe way. private void setTextUnsafeBtn_Click( object sender, EventArgs e) { this.demoThread = new Thread(new ThreadStart(this.ThreadProcUnsafe)); this.demoThread.Start(); } // This method is executed on the worker thread and makes // an unsafe call on the TextBox control. private void ThreadProcUnsafe() { this.textBox1.Text = "This text was set unsafely."; }
Thread-Safe Calls to a Windows Forms Control
To make a thread-safe call a Windows Forms control
-
Query the control's InvokeRequired property.
-
If InvokeRequired returns true, call Invoke with a delegate that makes the actual call to the control.
-
If InvokeRequired returns false, call the control directly.
In the following code example, this logic is implemented in a utility method called SetText. A delegate type named SetTextDelegate encapsulates the SetText method. When the TextBox control's InvokeRequired returns true, the SetText method creates an instance of SetTextDelegate and calls the form's Invoke method. This causes the SetText method to be called on the thread that created the TextBox control, and in this thread context the Text property is set directly.
// This event handler creates a thread that calls a // Windows Forms control in a thread-safe way. private void setTextSafeBtn_Click( object sender, EventArgs e) { this.demoThread = new Thread(new ThreadStart(this.ThreadProcSafe)); this.demoThread.Start(); } // This method is executed on the worker thread and makes // a thread-safe call on the TextBox control. private void ThreadProcSafe() { this.SetText("This text was set safely."); }
// This method demonstrates a pattern for making thread-safe // calls on a Windows Forms control. // // If the calling thread is different from the thread that // created the TextBox control, this method creates a // SetTextCallback and calls itself asynchronously using the // Invoke method. // // If the calling thread is the same as the thread that created // the TextBox control, the Text property is set directly. private void SetText(string text) { // InvokeRequired required compares the thread ID of the // calling thread to the thread ID of the creating thread. // If these threads are different, it returns true. if (this.textBox1.InvokeRequired) { SetTextCallback d = new SetTextCallback(SetText); this.Invoke(d, new object[] { text }); } else { this.textBox1.Text = text; } }
Thread-Safe Calls with BackgroundWorker
The preferred way to implement multithreading in your application is to use the BackgroundWorker component. The BackgroundWorker component uses an event-driven model for multithreading. The worker thread runs your DoWork event handler, and the thread that creates your controls runs your ProgressChanged and RunWorkerCompleted event handlers. Be careful not to call any of your controls from your DoWork event handler.
In the following code example, there is no work to perform asynchronously, so there is no DoWork event handler implementation. The TextBox control's Text property is set directly in the RunWorkerCompleted event handler.
// This event handler starts the form's // BackgroundWorker by calling RunWorkerAsync. // // The Text property of the TextBox control is set // when the BackgroundWorker raises the RunWorkerCompleted // event. private void setTextBackgroundWorkerBtn_Click( object sender, EventArgs e) { this.backgroundWorker1.RunWorkerAsync(); } // This event handler sets the Text property of the TextBox // control. It is called on the thread that created the // TextBox control, so the call is thread-safe. // // BackgroundWorker is the preferred way to perform asynchronous // operations. private void backgroundWorker1_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e) { this.textBox1.Text = "This text was set safely by BackgroundWorker."; }
ActiveX Controls on Windows Forms
If you are using ActiveX controls on a form, you may receive the cross-thread InvalidOperationException when running under the debugger. When this occurs, the ActiveX control does not support multi-threading. For more information about using ActiveX controls with Windows Forms, see Windows Forms and Unmanaged Applications.
If you are using Visual Studio, you can prevent this exception from occurring by disabling the Visual Studio hosting process.
Robust Programming
Caution |
|---|
| When you use multithreading of any sort, your code can be exposed to very serious and complex bugs. For more information, see Managed Threading Best Practices before implementing any solution that uses multithreading. |
See Also
/* hrmilo 2009
* Thread-safety for System.Windows.Forms
* For more information:
* "How to: Make Thread-Safe Calls to Windows Forms Controls"
*/
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Reflection;
namespace ThreadSafety
{
/// <summary>
/// Class demonstrates a utility to act upon Forms.Control properties.
/// Can be easily adopted to employ Forms.Control methods.
/// Example: "Text[ctrl]" get or set ctrl.Text using ctrl.Invoke if required.
/// </summary>
static class WFControl
{
/// <summary>
/// Perform the given Action delegate on behalf of the given System.Windows.Forms.Control.
/// Use this method when no return type is expected.
/// </summary>
/// <param name="ctrl">The Forms.Control to invoke upon.</param>
/// <param name="action">The control property or method to invoke.</param>
public static void PerformInvoke(Control ctrl, Action action)
{
if (ctrl.InvokeRequired)
{
ctrl.Invoke(action);
}
else
{
action();
}
}
/// <summary>
/// Perform the given Func delegate on behalf of the given System.Windows.Forms.Control.
/// Use this method when a return type is expected.
/// </summary>
/// <typeparam name="T">The type expected to be returned by the control property or method.</typeparam>
/// <param name="ctrl">The Forms.Control to invoke upon.</param>
/// <param name="action">The control property or method to invoke.</param>
/// <returns></returns>
public static T PerformInvoke<T>(Control ctrl, Func<T> action)
{
return ctrl.InvokeRequired ? (T)ctrl.Invoke(action) : action();
}
/// <summary>
/// Convenient generic access to a Forms.Control property.
/// The control is passed as an indexer argument.
/// Example: "Text[ctrl]" will get or set the ctrl.Text property.
/// </summary>
/// <typeparam name="T">The control property's type</typeparam>
public class Property<T>
{
/// <summary>
/// A Func delegate that employs the generic variant of PerformInvoke to emulate a property get method returning type T.
/// </summary>
Func<Control, T> getter;
/// <summary>
/// An Action delegate that employs the non-generic variant of PerformInvoke to emulate a property set method taking type T.
/// </summary>
Action<Control, T> setter;
/// <summary>
/// Func that performs for getter and setter both.
/// bool is false to invoke the getter, true to invoke the setter.
/// Control is the Forms.Control acted upon.
/// Type T is the argument type returned from the getter or passed to the setter.
/// </summary>
Func<bool, Control, T, T> accessor;
/// <summary>
/// Constructor with individualized get and set delegates
/// </summary>
/// <param name="_get">Func delegate to retrieve a control property</param>
/// <param name="_set">Action delegate to set a control property</param>
public Property(Func<Control, T> _get, Action<Control, T> _set)
{
if (_get == null)
{
getter = delegate(Control ctrl) { throw new NotImplementedException(); };
}
else
{
getter = _get;
}
if (_set == null)
{
setter = delegate(Control ctrl, T arg) { throw new NotImplementedException(); };
}
else
{
setter = _set;
}
}
/// <summary>
/// Constructor allowing one delegate to act as both a get delegate and set delegate.
/// Due to the clunky delegate employed, this constructor should not see too much use.
/// </summary>
/// <param name="_accessor">Func delegate providing get and set access to a control property</param>
public Property(Func<bool, Control, T, T> _accessor)
{
if (_accessor == null)
{
getter = delegate(Control ctrl) { throw new NotImplementedException(); };
setter = delegate(Control ctrl, T arg) { throw new NotImplementedException(); };
}
else
{
accessor = _accessor;
getter = delegate(Control ctrl) { return accessor(false, ctrl, default(T)); };//default(T) is ignored
setter = delegate(Control ctrl, T arg) { accessor(true, ctrl, arg); }; //ignores the accessor return value
}
}
/// <summary>
/// Provide convenient and clean way to pass a variety of Forms.Control objects.
/// </summary>
/// <param name="ctrl"></param>
/// <returns></returns>
public T this[Control ctrl]
{
get { return getter(ctrl); }
set { setter(ctrl, value); }
}
}
/// <summary>
/// Predefined Forms.Control properties many users will want to get or set.
/// To provide the look-and-feel of a property, private Property<T> members are defined and returned through public read-only class properties.
/// This bit of redirection could be eliminated.
/// </summary>
#region standard Control properties
private static Property<string> _text =
new Property<string>(
delegate(Control ctrl) { return PerformInvoke<string>(ctrl, delegate() { return ctrl.Text; }); },
delegate(Control ctrl, string arg) { PerformInvoke(ctrl, delegate() { ctrl.Text = arg; }); }
);
/// <summary>
/// get or set Forms.Control.Text
/// </summary>
public static Property<string> Text
{
get { return _text; }
}
private static Property<object> _tag =
new Property<object>(
delegate(Control ctrl) { return PerformInvoke<object>(ctrl, delegate() { return ctrl.Tag; }); },
delegate(Control ctrl, object arg) { PerformInvoke(ctrl, delegate() { ctrl.Tag = arg; }); }
);
/// <summary>
/// get or set Forms.Control.Tag
/// </summary>
public static Property<object> Tag
{
get { return _tag; }
}
private static Property<string> _name =
new Property<string>(
delegate(Control ctrl) { return PerformInvoke<string>(ctrl, delegate() { return ctrl.Name; }); },
delegate(Control ctrl, string arg) { PerformInvoke(ctrl, delegate() { ctrl.Name = arg; }); }
);
/// <summary>
/// get or set Forms.Control.Name
/// </summary>
public static Property<string> Name
{
get { return _name; }
}
private static Property<bool> _enabled =
new Property<bool>(
delegate(Control ctrl) { return PerformInvoke<bool>(ctrl, delegate() { return ctrl.Enabled; }); },
delegate(Control ctrl, bool arg) { PerformInvoke(ctrl, delegate() { ctrl.Enabled = arg; }); }
);
/* This demonstrates using the accessor delegate rather than separate getter and setter delegates */
//new Property<bool>(
// delegate(bool get_or_set, Control ctrl, bool arg)
// {
// if (get_or_set)
// {
// PerformInvoke(ctrl, delegate() { ctrl.Enabled = arg; }); //perform a set operation
// return false; //return is ignored
// }
// else //performa get operation
// {
// return PerformInvoke<bool>(ctrl, delegate() { return ctrl.Enabled; });
// }
// });
/// <summary>
/// get or set Forms.Control.Enabled
/// </summary>
public static Property<bool> Enabled
{
get { return _enabled; }
}
private static Property<Control.ControlCollection> _controls =
new Property<Control.ControlCollection>(
delegate(Control ctrl) { return PerformInvoke<Control.ControlCollection>(ctrl, delegate() { return ctrl.Controls; }); },
delegate(Control ctrl, Control.ControlCollection arg) { PerformInvoke(ctrl, delegate() { throw new NotSupportedException(); }); }
);
/// <summary>
/// get Forms.Control.Controls
/// </summary>
public static Property<Control.ControlCollection> Controls
{
get { return _controls; }
}
//add your own here, or define them outside of this class
#endregion
}
}
- 2/5/2011
- hrmilo
Where BackgroundWorker is being used to perform multiple GUI-related tasks, the delegate approach described here is cumbersome if it were to be used for each method call on a control. Pushing all of the GUI work to the progress callback is a weak solution since it is intended for numeric or simple text feedback. Pushing the GUI work to the work completed callback is weaker still because you lose the ability to see incremental changes to the GUI.
A simpler approach is to declare a generic delegate that matches the prototype of the worker methods called by the BackgroundWorker and simply re-thread the calls using the same worker method.
For example:
Create a set of background worker methods to be called one after another:
private void SetupWorker()
{
BackgroundWorker bw = newBackgroundWorker();
//Set up the background GUI work
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = false;
bw.DoWork += newDoWorkEventHandler(InitBasicData);
bw.DoWork += newDoWorkEventHandler(InitEquipmentData);
bw.DoWork += newDoWorkEventHandler(InitGraphArea);
bw.ProgressChanged += newProgressChangedEventHandler(HandleBackgroundProgressChanged);
bw.RunWorkerCompleted += newRunWorkerCompletedEventHandler(HandleBackgroundWorkCompleted);
bw.RunWorkerAsync();
}
Each worker method will normally look something like this:
private void InitBasicData(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
worker.ReportProgress(0, "Doing basic data tasks");
... and calling control methods which cause the cross-thread exception ...
}
To perform the same tasks using the same method without any elaborate setup, simply create a generic delegate at the form level:
//Rethreading delegate for worker tasks
delegate void InitWorkDelegate(object sender, DoWorkEventArgs e);
And then use the generic delegate in the worker method to rethread the worker method invocation itself:
private void InitBasicData(object sender, DoWorkEventArgs e)
{
//If we're off the main thread, use the generic delegate to push this call back to the main thread
if (InvokeRequired)
{
Invoke(new InitWorkDelegate(InitBasicData), new Object[] { sender, e });
return;
}
BackgroundWorker worker = sender as BackgroundWorker;
worker.ReportProgress(0, "Doing basic data tasks");
... and calling control methods which will no longer cause the cross-thread exception ...
}
- 3/25/2010
- Andrew Brundell
- 3/25/2010
- Andrew Brundell
- 2/2/2010
- Mr. Javaman
ANSWER - when the RunWorkerCompleted event handler is called, you are back on the calling thread.
- 8/22/2007
- JST39
- 10/29/2009
- Spivonious
When the calling thread and the thread that created the TextBox control acquire locks on a shared object, a potential deadlock may occurs.
This is because the calling thread waits for the thread that created the TextBox control to complete the execution of SetText() .
This is a resources acquisition graph that have a potential deadlock :
calling thread: a lock on a shared object X => Textbox thread execution of SetText()
Textbox thread: Textbox thread execution of SetText() => lock on a shared object X
- 6/7/2008
- Luca Minudel
To alleviate this problem, you can create another sub to make the call to Invoke, and call this sub asyncronously:
Private Sub SetText(ByVal str_text As String)
If Me.textbox1.InvokeRequired = True Then
'call an asyncronous invoke, by calling it then forcing a DoEvents
Dim asyncInvokeThread As New Threading.Thread(New Threading.ParameterizedThreadStart(AddressOf AsyncInvoke))
asyncInvokeThread.IsBackground = True
asyncInvokeThread.Start(str_text)
Application.DoEvents()
Else
me.textbox1.text=str_text
End If
End Sub
Private Sub AsyncInvoke(ByVal obj_text As Object)
Dim str_text As String = CStr(obj_text)
Dim d As New SetTextCallback(AddressOf SetText)
Me.Invoke(d, New Object() {str_message})
End Sub
- 8/30/2007
- Brian Henk
- 9/7/2007
- craigwardman
Note
Caution