© 野比 2012
源代码:点击下载
下面是动画,演示在多线程(无限循环+Thread.Sleep)情况下主界面操作不受影响。
多线程是一种提高程序运行效率和性能的常用技术。随着我们学习工作的深入,在编程中或多或少会涉及到需要多线程的情况。多数时候,我们的操作模式是后台线程中处理数据,计算结果,然后在前台界面(GUI)中更新显示。
在.NET Framework中,为了保证线程安全,避免出现访问竞争等问题,是不允许跨线程访问窗体控件的。如果强行访问,则会引发InvalidOperationException无效操作异常,如下图:
为了实现跨线程访问控件,.NET Framework为每个控件提供了InvokeRequired属性和Invoke方法。使用这些技巧,就可以实现我们在其他线程中直接修改界面的需要。看起来似乎很简单,但实际每次调用都有不少代码需要编写,还需要自行处理各种异常。下面是典型的调用例子:
public void DoWork() { if (control.InvokeRequired) { control.Invoke(DoWork); } else { // do work } }
该类实现非常简单,有效代码约150行,主要有以下3个方法:
1.Invoke
该方法可以调用主界面控件的某个方法,并返回方法执行结果。用法如下:
InvokeHelper.Invoke(<控件>, "<方法名称>", <参数>);
2.Get
该方法可以获取主界面控件的某个属性。用法如下:
InvokeHelper.Get(<控件>, "<属性名称>");
该方法可以设置主界面控件的某个属性。用法如下:
InvokeHelper.Set(<控件>, "<属性名称>", <属性值>);
/******************************************************************************* * InvokeHelper.cs * A thread-safe control invoker helper class. * ----------------------------------------------------------------------------- * Project:Conmajia.Controls * Author:Conmajia * Url:[email protected] * 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 } }
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++; } }
参考文献
[1] Sergiu Josan, Making Controls Thread-safely, May 2009
[2] vicoB, Extension of safeInvoke, July 2010
© 野比 2012