Multithreading helper class that provides access to its properties and methods when a thread is busy

© 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 InvalidOperationExceptioninvalid operation exceptions such as those shown in Figure 1.

InvalidOperationException Figure 1. Exceptions caused by cross-thread access

Although it is possible to achieve cross-thread access by setting Control.CheckForIllegalCrossThreadCallsto falsedisable 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.InvokeRequiredproperties and Control.Invokemethods. Typical code is as follows.

public void DoWork() {
    
    
	if (control1.InvokeRequired) {
    
    
		control1.Invoke(DoWork);
	} else {
    
    
		// work code
	}
}

For ease of use, a InvokeHelpermulti-thread auxiliary class named is given below, which is used to more concisely implement cross-thread access to main thread properties and methods.

InvokeHelperThe effective code is about 150 lines, and the main methods are:

  1. InvokeHelper.Invoke
    Call a method on the main thread and return the method execution result.
InvokeHelper.Invoke(<控件>, "<方法名>", <参数>);
  1. InvokeHelper.Get
    Returns a property value of the main thread.
InvokeHelper.Get(<控件>, "<属性名>");
  1. 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.

Figure 2. Accessing blocked thread properties and methods and updating the GUI

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

Guess you like

Origin blog.csdn.net/conmajia/article/details/135225125