码迷,mamicode.com
首页 > 其他好文 > 详细

💈 A Cross-Thread Call Helper Class

时间:2018-02-28 14:08:29      阅读:125      评论:0      收藏:0      [点我收藏+]

标签:ica   cross   opera   slow   too   params   eve   date   find   

Conmajia ? 2012, 2018

Introduction

When you are working on background threads and call frontend GUI methods, you could face one particular problem: invalid operation between threads. As in figure 1, this issue caused an InvalidOperationException. It occurs every time when accesses properties and methods from other threads but the one which owns these properties or methods.

技术分享图片
Fig. 1 Invalid Cross-Thread Call Demo

In .NET Framework, every Control contains an InvokeRequired property and an Invoke method to accomplish cross-thread calls. Below is a typical call of these property and method.

public void DoWork()  
{  
    if (control.InvokeRequired)  
    {  
        control.Invoke(DoWork);  
    }  
    else  
    {  
        // do work  
    }  
}

My InvokeHelper

I wrote an InvokeHelper class with which I can easily access cross-thread controls and get/set their properties. The InvokeHelper has 3 methods:

  1. Invoke() - to call methods of a control.
InvokeHelper.Invoke(<control>, "<method>"[, <param1>[,<param2>,...]]); 
  1. Get() - to get properties of a control.
InvokeHelper.Get(<control>, "<property>");  
  1. Set() - to set properties of a control.
InvokeHelper.Set(<control>, "<property>", <value>);  

Demonstration

In this demo, I used a forever looping background thread (t1) to show how the InvokeHelper helps background threads accessing the frontend GUI. The t1 updates GUI every 500 ms.

Thread t;  
private void button1_Click(object sender, EventArgs e)  
{  
    if (t == null)  
    {  
        t = new Thread(multithread);  
        t.Start();  
        label4.Text = string.Format(  
            "Thread state:\n{0}",  
            t.ThreadState.ToString()  
            );  
    }  
}  
  
public void DoWork(string msg)  
{  
    this.label3.Text = string.Format("Invoke method: {0}", msg);  
}  
  
int count = 0;  
void multithread()  
{  
    while (true)  
    {  
        InvokeHelper.Set(this.label1, "Text", string.Format("Set value: {0}", count));  
        InvokeHelper.Set(this.label1, "Tag", count);  
        string value = InvokeHelper.Get(this.label1, "Tag").ToString();  
        InvokeHelper.Set(this.label2, "Text",  
            string.Format("Get value: {0}", value));  
  
        InvokeHelper.Invoke(this, "DoWork", value);  
  
        Thread.Sleep(500);  
        count++;  
    }  
}

The result is show in animated figure 2. Obviously, the frontend was not blocked despite t1 was never idle.

技术分享图片
Fig. 2 `InvokeHelper` Demonstration (animation)

Other Approaches

There is a built-in switch that disables the cross-thread access check: CheckForIllegalCrossThreadCalls. I tested it and found it a bit slower than my InvokeHelper in speed. Figure 3 shows the test result.

技术分享图片
Fig. 3 Test Result for `CheckForIllegalCrossThreadCalls`

The whole procedure of the test is recorded in figure 4. The video lasts for 8‘51".

技术分享图片
Fig. 4 Test Video for `CheckForIllegalCrossThreadCalls` (8‘51")

Appendix

Here is the full source code of the InvokeHelper. And the zipped project files can be found in here: Download

/******************************************************************************* 
 * InvokeHelper.cs 
 * A thread-safe control invoker helper class. 
 * -----------------------------------------------------------------
 * Project: Conmajia.Controls 
 * Author: Conmajia 
 * History: 
 *      Aug. 4, 2012 
 *      Added support for "Non-control" controls (such as ToolStripItem).    
 *      Aug. 4, 2012 
 *      Initiated. 
 ******************************************************************************/  
using System;  
using System.Collections.Generic;  
using System.Reflection;  
using System.Text;  
using System.Windows.Forms;  
namespace InvokerHelperDemo  
{  
    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.");  
        }  
        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);  
                    if (mi != null)  
                        return mi.Invoke(control, args);  
                    else  
                        throw new InvalidOperationException("Invalid method.");  
                }  
            else  
                throw new ArgumentNullException("Invalid argument.");  
        }  
        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.");  
        }  
        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.");  
        }  
    }  
}

References

  1. Sergiu Josan, Making Controls Thread-safely, May 2009
  2. vicoB, Extension of safeInvoke, July 2010

The End. \(\Box\)

💈 A Cross-Thread Call Helper Class

标签:ica   cross   opera   slow   too   params   eve   date   find   

原文地址:https://www.cnblogs.com/conmajia/p/invokehelper-class.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!