© Conmajia 2012-2023
First release: 2012.08.05 00:44:45
Miscellaneous Series: 128.1
Multithreading is a common technology that makes full use of the central processing unit to run slices to improve program running efficiency and performance. Usually, high-performance applications adopt a front-end and back-end approach, that is, high-load calculations are handed over to background threads for calculation, while the foreground thread—usually a graphical user interface (GUI)—is responsible for refreshing and displaying calculation results.
In the .NET Framework, due to thread safety considerations, cross-thread access to GUI properties and methods is not allowed by default. Cross-thread access will trigger InvalidOperationException
invalid operation exceptions such as those shown in Figure 1.
Although it is possible to achieve cross-thread access by setting Control.CheckForIllegalCrossThreadCalls
to false
disable this check, this will introduce serious thread safety hazards.
Taking the GUI thread as an example, safer cross-thread access techniques are usually implemented using Control.InvokeRequired
properties and Control.Invoke
methods. Typical code is as follows.
public void DoWork() {
if (control1.InvokeRequired) {
control1.Invoke(DoWork);
} else {
// work code
}
}
For ease of use, a InvokeHelper
multi-thread auxiliary class named is given below, which is used to more concisely implement cross-thread access to main thread properties and methods.
InvokeHelper
The effective code is about 150 lines, and the main methods are:
InvokeHelper.Invoke
Call a method on the main thread and return the method execution result.
InvokeHelper.Invoke(<控件>, "<方法名>", <参数>);
InvokeHelper.Get
Returns a property value of the main thread.
InvokeHelper.Get(<控件>, "<属性名>");
InvokeHelper.Set
Set a property value of the main thread.
InvokeHelper.Set(<控件>, "<属性名>", <属性值>);
The complete implementation source code is as follows.
/*******************************************************************************
* InvokeHelper.cs
* A thread-safe control invoker helper class.
* -----------------------------------------------------------------------------
* Project:Conmajia.Controls
* Author:Conmajia
* History:
* 4th Aug., 2012
* Added support for "Non-control" controls (such as ToolStripItem).
* 4th Aug., 2012
* Initiated.
******************************************************************************/
// A thread-safe control invoker helper class.
public class InvokeHelper {
private delegate object MethodInvoker(
Control control, string methodName, params object[] args);
private delegate object PropertyGetInvoker(
Control control, object noncontrol, string propertyName);
private delegate void PropertySetInvoker(
Control control, object noncontrol, string propertyName, object value);
private static PropertyInfo GetPropertyInfo(
Control control, object noncontrol, string propertyName) {
if (control != null && !string.IsNullOrEmpty(propertyName)) {
PropertyInfo pi = null;
Type t = null;
if (noncontrol != null)
t = noncontrol.GetType();
else
t = control.GetType();
pi = t.GetProperty(propertyName);
if (pi == null)
throw new InvalidOperationException(
string.Format(
"Can't find property {0} in {1}.",
propertyName, t.ToString()));
return pi;
} else
throw new ArgumentNullException("Invalid argument.");
}
// Invoke a method
public static object Invoke(Control control, string methodName, params object[] args) {
if (control != null && !string.IsNullOrEmpty(methodName))
if (control.InvokeRequired)
return control.Invoke(
new MethodInvoker(Invoke),
control, methodName, args);
else {
MethodInfo mi = null;
if (args != null && args.Length > 0) {
Type[] types = new Type[args.Length];
for (int i = 0; i < args.Length; i++)
if (args[i] != null)
types[i] = args[i].GetType();
mi = control.GetType().GetMethod(methodName, types);
} else
mi = control.GetType().GetMethod(methodName);
// check method info you get
if (mi != null)
return mi.Invoke(control, args);
else
throw new InvalidOperationException("Invalid method.");
}
else
throw new ArgumentNullException("Invalid argument.");
}
// Get the value of a property
public static object Get(Control control, string propertyName) {
return Get(control, null, propertyName);
}
public static object Get(Control control, object noncontrol, string propertyName) {
if (control != null && !string.IsNullOrEmpty(propertyName))
if (control.InvokeRequired)
return control.Invoke(new PropertyGetInvoker(Get),
control,
noncontrol,
propertyName
);
else {
PropertyInfo pi = GetPropertyInfo(control, noncontrol, propertyName);
object invokee = (noncontrol == null) ? control : noncontrol;
if (pi != null)
if (pi.CanRead)
return pi.GetValue(invokee, null);
else
throw new FieldAccessException(
string.Format(
"{0}.{1} is a write-only property.",
invokee.GetType().ToString(),
propertyName
));
return null;
}
else
throw new ArgumentNullException("Invalid argument.");
}
// Set the value of a property
public static void Set(Control control, string propertyName, object value) {
Set(control, null, propertyName, value);
}
public static void Set(Control control, object noncontrol, string propertyName, object value) {
if (control != null && !string.IsNullOrEmpty(propertyName))
if (control.InvokeRequired)
control.Invoke(new PropertySetInvoker(Set),
control,
noncontrol,
propertyName,
value
);
else {
PropertyInfo pi = GetPropertyInfo(control, noncontrol, propertyName);
object invokee = (noncontrol == null) ? control : noncontrol;
if (pi != null)
if (pi.CanWrite)
pi.SetValue(invokee, value, null);
else
throw new FieldAccessException(
string.Format(
"{0}.{1} is a read-only property.",
invokee.GetType().ToString(),
propertyName
));
}
else
throw new ArgumentNullException("Invalid argument.");
}
}
With this multi-thread helper class, users can realize that when a thread executes blocking instructions and cannot respond to other instructions, the GUI thread can still access its properties and methods to update the user interface. The animation in Figure 2 demonstrates this process.
The blocking code run by the worker thread is as follows.
// Blocking
int count = 0;
while(true) {
// ...
Thread.Sleep(1000);
count++;
}
References
[1] Sergiu Josan, Making controls thread-safely , May 2009.
[2] vicoB, Extension of safeInvoke , July 2010.
© Conmajia 2012