当前位置:首页 >.NET > 正文内容

InvokeHelper,让跨线程访问/修改主界面控件不再麻烦​

大滑稽11年前 (2014-03-24).NET1286

事实上,本文内容很简单且浅显,所以取消前戏,直接开始。。

源代码:在本文最后

这里是一张动画,演示在多线程(无限循环+Thread.Sleep)情况下主界面操作不受影响。



多线程是一种提高程序运行效率和性能的常用技术。随着我们学习工作的深入,在编程中或多或少会涉及到需要多线程的情况。多数时候,我们的操作模式是后台线程中处理数据,计算结果,然后在前台界面(GUI)中更新显示。

在.NET Framework中,为了保证线程安全,避免出现访问竞争等问题,是不允许跨线程访问窗体控件的。如果强行访问,则会引发InvalidOperationException无效操作异常,如下图:

为了实现跨线程访问控件,.NET Framework为每个控件提供了InvokeRequired属性和Invoke方法。使用这些技巧,就可以实现我们在其他线程中直接修改界面的需要。看起来似乎很简单,但实际每次调用都有不少代码需要编写,还需要自行处理各种异常。下面是典型的调用例子:

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

为了便于使用,我封装了实现细节,在这里给出一个InvokeHelper类,使用该类即可方便地实现跨线程调用主界面控件方法、获取/设置控件属性等功能。
该类实现非常简单,有效代码约150行,主要有以下3个方法:

1.Invoke

该方法可以调用主界面控件的某个方法,并返回方法执行结果。用法如下:
InvokeHelper.Invoke(<控件>, "<方法名称>", <参数>);

其中“参数”为参数列表,支持0个或多个参数。

2.Get

该方法可以获取主界面控件的某个属性。用法如下:
InvokeHelper.Get(<控件>, "<属性名称>");


3.Set
该方法可以设置主界面控件的某个属性。用法如下:
InvokeHelper.Set(<控件>, "<属性名称>", <属性值>);


下面是整个类的实现代码。最后是一个演示用的例子。

/******************************************************************************* 
 * InvokeHelper.cs 
 * A thread-safe control invoker helper class. 
 * ----------------------------------------------------------------------------- 
 * Project:Conmajia.Controls 
 * Author:Conmajia 
 * Url:conmajia@gmail.com 
 * History: 
 *      4th Aug., 2012 
 *      Added support for "Non-control" controls (such as ToolStripItem). 
 *       
 *      4th Aug., 2012 
 *      Initiated. 
 ******************************************************************************/  
using System;  
using System.Collections.Generic;  
using System.Reflection;  
using System.Text;  
using System.Windows.Forms;  
  
namespace InvokerHelperDemo  
{  
    /// <summary>  
    /// A thread-safe control invoker helper class.  
    /// </summary>  
    public class InvokeHelper  
    {  
        #region delegates  
        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);  
        #endregion  
 
        #region static methods  
        // helpers  
        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.");  
        }  
  
        // outlines  
        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.");  
        }  
  
        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.");  
        }  
        #endregion  
    }  
}


下面是一个演示用的例子。在该例子中,创建了一个永久循环的线程,该线程每隔500毫秒修改一次界面显示。主要代码如下:
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++;  
    }  
}


详细代码请参阅源代码。运行后效果正常,尽管线程t是无限循环的线程,但主界面并不受其阻塞,操作一切正常。


源代码:
点击下载
 

 

扫描二维码推送至手机访问。

版权声明:本文由第★一★次发布,如需转载请注明出处。

本文链接:https://wpers.net/post/52.html

“InvokeHelper,让跨线程访问/修改主界面控件不再麻烦​” 的相关文章

Cbo控件数据源绑定

 //Cbo控件数据源绑定DataTable DtType = noteType.GetTypeList("");         ...

C#遍历控件的方法

首先,要想遍历,就必须找到你想找的表单里面的所有控件,然后一个个的逐一比对,当找到了你需要的控件的时候,再做你需要的操作。1、foreach方法foreach (Control control in ...

Linq读写XML

         private List<News> GetNews(string html)    &n...

修改注册表限制软件使用次数

 private void Form1_Load(object sender, System.EventArgs e){RegistryKey RootKey,RegKey; //项名为:HKEY_CURRENT_USER\So...

c#中分页显示数据

     //c#中分页显示数据    public partial class Form1 : Form    {  ...

C#修改浏览器主页

string key = @"HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main";      &n...