zoukankan      html  css  js  c++  java
  • 💈 线程间互访助手类 (EN)

    Conmajia © 2012, 2018
    Published on August 5th, 2012
    Updated on February 2nd, 2019

    Introduction

    While working on background threads, you may encounter updating values of frontend GUI controls. Then you probably will face one particular problem: invalid operation between threads. As shown in figure 1, this issue throws an InvalidOperationException. It occurs every time when accesses properties/methods from other threads but the one which owns them.

    Fig. 1 Invalid Cross-Thread Call (screenshot)

    In .NET Framework, version 2.0 as I refer, every Control class contains an InvokeRequired property and an Invoke method to accomplish cross-thread operations. Some typical call code is listed below.

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

    My Approach

    I wrote a helper class InvokeHelper which granted me the power to access assets between different threads. The class 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 the demo, I used a forever looping background thread (t) to show how the InvokeHelper helps threads to interact. Thread t updates the frontend thread (the GUI) every 500 milliseconds.

    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:
    {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++;
        }
    }
    

    Results shown in animated figure 2. Obviously, the frontend is not blocked despite t never stops.

    Fig. 2 InvokeHelper Demonstration (animation)

    Other Approaches

    There is a built-in option in the VisualStudio IDE that disables the cross-thread access check: CheckForIllegalCrossThreadCalls. I found it slightly slower than my helper class. 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

    Source code of the InvokeHelper is listed below. Zipped .NET project files: Download

    
    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)

  • 相关阅读:
    flask
    redis实战之事物和持久化
    vue 工程从window 到mac
    mysql5.7 group by
    Error creating bean with name 'org.apache.cxf.spring.boot.autoconfigure.CxfAutoConfiguration
    当我回过头
    springmvc 接收json类型的数据封装到map中
    linux 下 home 目录磁盘爆满,rm 后仍然不行
    springboot启动时的一个bug
    vue 使用webpack 打包 出现UnhandledPromiseRejectionWarning: Error: "dependency" is not a valid chunk sort mode at HtmlWebpackPlugin.sortEntryChunks
  • 原文地址:https://www.cnblogs.com/conmajia/p/invokehelper-class.html
Copyright © 2011-2022 走看看