博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
InvokeHelper:多线程修改主界面控件属性并调用其中方法
阅读量:2454 次
发布时间:2019-05-10

本文共 8335 字,大约阅读时间需要 27 分钟。

© 野比 2012

源代码:

下面是动画,演示在多线程(无限循环+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{    ///     /// A thread-safe control invoker helper class.    ///     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是无限循环的线程,但主界面并不受其阻塞,操作一切正常。

参考文献

[1] Sergiu Josan, Making Controls Thread-safely, May 2009

[2] vicoB, Extension of safeInvoke, July 2010

© 野比 2012

转载地址:http://sxdhb.baihongyu.com/

你可能感兴趣的文章
sh脚本和bash脚本_使用此简单的Bash脚本在家打印双面文档
查看>>
raspberry pi_使用Raspberry Pi构建感知假肢
查看>>
raspberry pi_一个方便的实用程序,用于创建Raspberry Pi SD卡图像
查看>>
盲打每分钟资源10几个字_每个系统管理员应了解的10个资源
查看>>
横向扩展基础架构_您应该使用的7种基础架构性能和扩展工具
查看>>
bbc 王超_BBC Microbit入门
查看>>
sysadmin默认密码_Sysadmin感谢日的礼物想法
查看>>
如何在Kubernetes上找到您的Jenkins管理员密码
查看>>
ansible操作数据库_以数据为中心的Ansible修补系统方法
查看>>
c++编写音乐播放器_为什么此开发人员编写了快速响应的音乐播放器
查看>>
github pages_使用此HTTP hack重定向GitHub Pages网站
查看>>
python tox_使用tox自动化Python代码测试
查看>>
python cython_使用Cython为Python编写更快的C扩展
查看>>
flake8变量未使用_使用flake8确保Python代码的一致性
查看>>
ssh与gpg区别_如何使用GPG密钥启用SSH访问进行身份验证
查看>>
apm 韩国开源项目_韩国的开源状态
查看>>
mac上将视频变小_在Mac上将Python 3设置为默认的正确和错误方法
查看>>
java jnlp_Java SE 11删除JNLP的更好解决方案
查看>>
devops 中台_DevOps中的门控生产
查看>>
keil 开源替代_您需要替代开源的哪些专有工具?
查看>>