- 编 曲
- 浅倉大介
- 歌曲时长
- 4:18
- 歌曲原唱
- T.M.Revolution
- 发行时间
- 2002-10-30
- 谱 曲
- 浅倉大介
- 音乐风格
- 摇滚
- 外文名称
- INVOKE
- 所属专辑
- INVOKE
- 歌曲语言
- 日语
- 填 词
- 井上秋緒
-
2021-03-16 16:59:25
Java 动态代理,invoke() 自动调用原理,invoke() 参数
本文介绍了静态代理和动态代理的概念,并且用代码、注释、图示和源码的方式来分析动态代理原理和invoke()自动调用原理。
学习动态代理,先从静态代理入手
静态代理
假如现在我们面临一个需求,把一个项目中所有访问数据库的方法都记录日志。最直接的方法是修改所有代码,在每个方法里面都加入写日志代码:
public class Dao { User findUserbyId(int id) { // 业务代码 xxx; // 加入写日志代码 xxx; } }
但是这样工作量会很大,并且模块之间是耦合的,比如下次的需求是修改记录日志的内容,那么又要去修改所有业务代码,显然这种方法是不可取的。
如果不可以修改业务代码呢?
很容易就想到静态代理,写代理类对业务代码进行调用,我们用老师教学生举例子:
/** * 老师接口,老师可以教学生 */ public interface Teacher { void teach(); }
/** * 老师接口的实现类,具体实现 teach() 方法 */ public class RealTeacher implements Teacher { @Override public void teach() { System.out.println("I am a teacher, I am teaching!"); } }
/** * 老师代理对象,增强 teach() 方法 */ public class TeacherProxy implements Teacher { Teacher teacher = new RealTeacher(); @Override public void teach() { // 这里 dosomething() 可以实现一些代理类的功能 dosomething(); // 这里执行 RealTeacher 的 teach() 方法 teacher.teach(); dosomething(); } }
/** * 测试代理类 */ public class ProxyTest { public static void main(String args[]) { Teacher teacher = new TeacherProxy(); // 这里执行代理类的 teach() 方法 teacher.teach(); } }
用图示表示上面静态代理的代码,可以描述为:
动态代理
既然为每一个需要代理的对象(下文通称为目标对象)都编写相应的代理类很麻烦,那么能不能不写死代理对象和目标对象,只写一个代理对象就可以使用不同的目标对象呢?
答案是可以的,就是用动态代理。动态代理的代理对象不依赖于目标对象,不和目标对象耦合。其原理是把目标对象的类型信息(比如接口)作为参数,利用 Java 支持的反射来创建代理对象,这样,就解除了代理对象和目标对象的耦合,一个代理对象可以适用一些列的目标对象。用图表示为:
如上图所示,目标对象作为一个参数,动态代理得到这个参数之后分成三步走:- 获得目标对象的接口信息和类加载器,然后根据这两个信息来反射得到代理类的 Class
- 获得目标对象的 Class
- 根据1 2两步获得的接口,代理类 Class,目标类 Class 实例化一个代理对象
这样, 就创建了一个代理对象。从图中可以看出,动态代理并没有和目标对象绑定,而是把目标对象作为一个参数。
JDK 提供了 java.lang.reflect.InvocationHandler 接口和 java.lang.reflect.Proxy 类来实现上述反射等功能,其中 java.lang.reflect.InvovationHandler 接口用于绑定目标对象需要增强的方法,java.lang.reflect.Proxy 提供静态方法 NewProxyInstance() 用于创建一个代理类实例,这样,这个代理类实习就被创建出来并且通过嵌入 InvocationHandler 绑定了目标类的方法。
上面的静态代理代码的例子改成动态代理:/** * 老师接口,老师可以教学生 */ public interface Teacher { void teach(); }
/** * 老师接口的实现类,具体实现 teach() 方法 */ public class RealTeacher implements Teacher { @Override public void teach() { // 老师正在教书 System.out.println("I am a teacher, I am teaching!"); } }
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 代理类,根据接口等信息动态地创建代理对象,其中 MyProxy 是 InvocationHandler 的实现类,用于绑定方法 */ public class MyProxy implements InvocationHandler { // tar用于接收目标类的参数 private Object tar; // 绑定目标类,根据目标类的类加载器和接口创建代理对象,并返回 public Object bind(Object target) { this.tar = target; // 注意:此处代码返回代理类 return Proxy.newProxyInstance(tar.getClass().getClassLoader(), tar.getClass().getInterfaces(), this); } // invoke() 方法用于方法的增强 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; // 执行一些方法 System.out.println("Do something before"); // 目标类的方法执行,这里实际上是执行目标对象的方法, // 也就是 bind(Object target)参数 object target 的方法 result = method.invoke(tar, args); System.out.println("Do something after"); return result; } }
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class ProxyTest { public static void main(String[] args) throws Throwable { MyProxy proxy = new MyProxy(); // 传入目标对象,进行绑定 Teacher teacher = (Teacher)proxy.bind(new RealTeacher()); // 执行代理对象的方法 teacher.teach(); } }
测试函数的输出应该为:
Do something before
I am a teacher, I am teaching!
Do something after改写成内部类的方式可以更清楚的看到:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class ProxyTest { public static void main(String[] args) throws Throwable { // 这里能清楚地看到,目标对象 RealTeacher 是一个纯粹的参数 Teacher teacher = (Teacher)getProxy(new RealTeacher()); // teacher 是接口 Teacher 的一个实现类, // 完全从多态的角度也能想到执行的实际上是 RealTeacher 的 teach() 方法 teacher.teach(); } public static Object getProxy(Object target) throws Throwable{ return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; System.out.println("Do something before"); result = method.invoke(target, args); System.out.println("Do something after"); return result; } }); } }
此源代码在 GitHub 上。
目标对象 RealTeacher 可以看成是一个纯粹的参数,不和代理类耦合。
在应用的时候,可以把具体实现过程忽略,把Teacher teacher = (Teacher)getProxy(new RealTeacher());
看成一个黑箱,它输入一个目标类作为参数,返回一个实现了 Teacher 接口的代理类。到此为止,我们成功使用了动态代理,在没有和目标类 RealTeacher 绑定的情况下,创建了一个代理类,增强了 teach() 方法。
进一步深究代理类 teacher,invoke()
为了进一步探究生成的代理类 teacher 到底是什么一个情况,我们输出 teach 的一些类型信息:
public static void main(String[] args) throws Throwable { // 这里能清楚地看到,目标对象 RealTeacher 是一个纯粹的参数 Teacher teacher = (Teacher)getProxy(new RealTeacher()); // teacher 是接口 Teacher 的一个实现类, // 完全从多态的角度也能想到执行的实际上是 RealTeacher 的 teach() 方法 teacher.teach(); System.out.println(teacher.getClass().getSuperclass());//输出是class java.lang.reflect.Proxy Class<?>[] interfaces = teacher.getClass().getInterfaces(); for(Class<?> i : interfaces){ System.out.println(i);// 输出是interface Teacher } Method[] methods = teacher.getClass().getDeclaredMethods(); for (Method method : methods) { System.out.println(method); // 输出是: // public final boolean com.sun.proxy.$Proxy0.equals(java.lang.Object) // public final java.lang.String com.sun.proxy.$Proxy0.toString() // public final int com.sun.proxy.$Proxy0.hashCode() // public final void com.sun.proxy.$Proxy0.teach() } }
由上述输出可以发现,
Teacher teacher = (Teacher)getProxy(new RealTeacher());
创建的代理类 teacher 实际是名字是 $Proxy0,它父类 Proxy,实现了 Teacher 接口,实现了 teach() 方法,所以代码
teacher.teach();
理所当然可以执行。
但是,我们知道代码 teacher.teach(); 最终执行了invoke()方法,那么这个teach()方法到底怎么和invoke()有关联的呢?
我们打开 teach 的方法 Proxy.newProxyInstance()源码的注释,关键的是这几行:
* Returns a proxy instance for the specified interfaces * that dispatches method invocations to the specified invocation * handler. @param loader the class loader to define the proxy class * @param interfaces the list of interfaces for the proxy class * to implement * @param h the invocation handler to dispatch method invocations to * @return a proxy instance with the specified invocation handler of a * proxy class that is defined by the specified class loader * and that implements the specified interfaces
再看 Proxy 类的注释:
* {@code Proxy} provides static methods for creating objects that act like instances * of interfaces but allow for customized method invocation. * To create a proxy instance for some interface {@code Foo}: * <pre>{@code * InvocationHandler handler = new MyInvocationHandler(...); * Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), * new Class<?>[] { Foo.class }, * handler); * }</pre> * * <p> * A <em>proxy class</em> is a class created at runtime that implements a specified * list of interfaces, known as <em>proxy interfaces</em>. A <em>proxy instance</em> * is an instance of a proxy class. * * Each proxy instance has an associated <i>invocation handler</i> * object, which implements the interface {@link InvocationHandler}. * A method invocation on a proxy instance through one of its proxy * interfaces will be dispatched to the {@link InvocationHandler#invoke * invoke} method of the instance's invocation handler, passing the proxy * instance, a {@code java.lang.reflect.Method} object identifying * the method that was invoked, and an array of type {@code Object} * containing the arguments. The invocation handler processes the * encoded method invocation as appropriate and the result that it * returns will be returned as the result of the method invocation on * the proxy instance. *
由注释发现,Proxy.newProxyInstance()方法返回一个代理对象的实例,这个实例是和一个特殊的 InvocationHandler 相关的,再回看我们写的代码:
我们实例化的匿名内部类 InvocationHandler 正是有 invoke() 方法,所以刚才的问题:那么这个teach()方法到底怎么和invoke()有关联的呢?
是因为代理对象实例 teacher 的父类是 Proxy,由 Proxy 去和我们实例化的匿名内部类 InvocationHandler 去关联
再看实例对象 teacher(也就是 $Proxy0 )的反编译代码:(此代码来自这里)
public final class $Proxy0 extends Proxy implements Subject { private static Method m1; private static Method m0; private static Method m3; private static Method m2; static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); m3 = Class.forName("***.RealSubject").getMethod("request", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); } catch (NoSuchMethodException nosuchmethodexception) { throw new NoSuchMethodError(nosuchmethodexception.getMessage()); } catch (ClassNotFoundException classnotfoundexception) { throw new NoClassDefFoundError(classnotfoundexception.getMessage()); } } //static public $Proxy0(InvocationHandler invocationhandler) { super(invocationhandler); } @Override public final boolean equals(Object obj) { try { return ((Boolean) super.h.invoke(this, m1, new Object[] { obj })) .booleanValue(); } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } @Override public final int hashCode() { try { return ((Integer) super.h.invoke(this, m0, null)).intValue(); } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final void teach() { try { super.h.invoke(this, m3, null); return; } catch (Error e) { } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } @Override public final String toString() { try { return (String) super.h.invoke(this, m2, null); } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } }
也验证了当执行 teacher.teach() 的时候,也就是执行 $Proxy0.teach() 的时候,会执行 super.h.invoke(),也就是执行父类 Proxy 的 invoke(),而父类 Proxy 是和 InvocationHandler 是关联的,总结为:
teacher.teach() --> $Proxy0.teach() --> super.h.invoke() --> proxy.invoke() --> invocationHandler.invoke()
图示可以表示为:
invoke() 的参数
从代码可以看出,invoke() 方法有三个参数:
- Object proxy
- Method method
- Object[] args
我们在 invoke() 中输出 proxy 的名字:
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 输出proxy名字 System.out.println(proxy.getClass().getName()); Object result = null; System.out.println("Do something before1"); result = method.invoke(target, args); System.out.println("Do something after1"); return result; } });
输出是:
上文说代理类名字是 $Proxy0,所以实际上 proxy 参数就是代指反射创建的代理类 $Proxy0,之所以不用 this,因为 this 代表的是 InvocationHandler() 这个匿名内部类的实例。同样的,我们看 $Proxy0 的反编译代码:
这个代码中的this就是 $Proxy0 对象本身,也是作为 invoke() 的第一个参数,也就是 proxy 进行参数传递。所以也能得到 proxy 参数就是代指反射创建的代理类 $Proxy0 的结论。对于第二个参数 method,我们看 Method 的注释:
* A {@code Method} provides information about, and access to, a single method * on a class or interface. The reflected method may be a class method * or an instance method (including an abstract method).
可以看出第二个参数 method 用于绑定的目标类的方法
第三个参数是方法的参数
总结
代理模式是一种不改变源代码对方法进行增强的好方法,但是静态代理代理类和目标类是绑定耦合的。
动态代理的特点是:
- 利用目标类的接口信息,通过反射创建代理类
- 可以把代理类固定下来,不因为业务代码量庞大而复杂,便于扩展、修改和维护。
- 方便实现 RPC 和 AOP
- 源码难以理解
更多相关内容 -
Invoke-WCMDump结合powershell进行密码获取
2020-03-26 13:52:28Ivoke-WCMDump 什么是Credential Manager ...从Credential Manager导出Windows凭据的Powershell脚本 https://github.com/peewpw/Invoke-WCMDump PS>Import-Module .\Invoke-WCMDump.ps1 ...Invoke-WCMDump -
invoke
2021-03-28 18:56:16调用 INVOKE的项目描述 这是INVOKE的自动生成的项目描述。 请随时关注更新:-) 执照 所有软件均受版权保护,并受Apache许可证2.0版的保护。 -
c# Invoke和BeginInvoke 区别分析
2020-09-04 02:13:36主要介绍了c# Invoke和BeginInvoke 区别分析,需要的朋友可以参考下 -
详解Java中Method的Invoke方法
2020-08-29 02:55:24主要介绍了详解Java中Method的Invoke方法,需要的朋友可以参考下 -
Java Method类及invoke方法原理解析
2020-08-18 17:11:44主要介绍了Java Method类及invoke方法原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 -
P/Invoke Interop Assistant交互助手
2021-04-18 15:27:54可以把C/C++中的数据类型、结构体数据格式转换为C#或者VB版本中的对应格式类型。很方便 -
C#中Invoke 和 BeginInvoke 的真正涵义
2020-09-04 02:14:13主要介绍了C#中Invoke 和 BeginInvoke 的真正涵义,需要的朋友可以参考下 -
Java反射机制及Method.invoke详解
2020-09-03 21:52:05主要介绍了Java反射机制及Method.invoke详解,本文讲解了JAVA反射机制、得到某个对象的属性、得到某个类的静态属性、执行某对象的方法、执行某个类的静态方法等内容,需要的朋友可以参考下 -
tag_invoke:我的C ++ 20实现tag_invoke,在WG21论文P1895R0中进行了描述
2021-04-04 13:30:26tag_invoke 我的C ++ 20实现tag_invoke,在WG21论文P1895R0中进行了描述。 -
vue-router-invoke-webpack-plugin:根据文件目录自动生成vue-router的路由
2021-05-14 17:26:48npm install vue - router - invoke - webpack - plugin - D cnpm cnpm install vue - router - invoke - webpack - plugin - D 纱 yarn add vue - router - invoke - webpack - plugin - D 什么是自动生成路线 ... -
invoke_impl.rar
2020-04-24 16:12:02std::invoke函数模板的两种实现源码,通过实现invoke,对C++模板编程,SFINAE机制,编译期分支,类型转发,类成员指针都有非常好的理解。第一种实现方式需要C++17,VS2019编译运行通过,第二种方式VS2017上编译运行... -
invoke_call:Ruby包裹的命令行工具,用于拨打SIP电话
2021-05-07 10:49:30$ cd invoke_call $ bundle update $ bundle install $ rake build $ gem install pkg/invoke_call-0.1.0.gem 编译必要的二进制文件 $ rake compile_binaries 可能(可能)会发出大量警告。 只要它不会失败,一切都... -
Invoke-ARPScan.ps1
2021-04-04 22:16:05Invoke-ARPScan.ps1 -
Invoke-WCMDump-master1.rar
2021-06-10 12:59:43使用 Invoke-WCMDump 加powershell 获取windows凭证密码 使用文档地址:https://blog.csdn.net/caperxi/article/details/117775522 -
invoke-release:一组命令行工具,可帮助软件工程师以一致的方式快速,轻松地发布Python项目
2021-05-28 04:35:58Invoke Release是一组命令行工具,可帮助软件工程师快速,轻松且以一致的方式发布Python项目。 它有助于确保您的项目的版本标准在组织的所有项目中都相同,并最大程度地减少了发行期间可能发生的错误。 它使用Git... -
Invoke-Parallel:通过简化的多线程加速PowerShell
2021-02-21 09:02:50并行调用 该函数将接受一个脚本或脚本块,并...# Use Invoke-Parallel with variables in your session $Number = 2 1 .. 10 | Invoke-Parallel - ImportVariables - ScriptBlock { $Number * $_ } # Use the $Usin -
C#窗体中Invoke和BeginInvoke方法详解.docx
2020-10-10 22:43:40PAGE PAGE #/ 6 在Invoke或者Beginlnvoke的使用中无一例外地使用了委托 Delegate,至于 委托的本质请参考我的另一随笔 一为什么 Control 类提供了 Invoke 和 Beginlnvoke机制? 1 windows 程序消息机制 WindowsGUl ... -
PHP 5.3新增魔术方法__invoke概述
2020-10-25 15:35:28主要介绍了PHP 5.3新增魔术方法__invoke,需要的朋友可以参考下 -
Invoke跨线程调用的代码
2020-04-10 19:22:47忽略跨线程访问的错误 CheckForIllegalCrossThreadCalls = false,但是这个方法很不稳定,使用invoke方法解决跨线程访问的问题,里边有2个例子, 通过自己额外创建的线程改变label控件中的内容从而验证invoke方法 -
InvocationHandler中invoke()方法的调用问题分析
2020-08-28 17:50:44主要介绍了InvocationHandler中invoke()方法的调用问题分析,具有一定参考价值,需要的朋友可以了解下。 -
Excel、Word转PDF时,异常com.jacob.com.ComFailException: Invoke of: SaveAs
2019-04-20 01:29:13NULL 博文链接:https://liumayulingyan.iteye.com/blog/1900122 -
Invoke 与BeginInvoke的区别
2016-01-25 18:52:35让你快速掌握INvoke与BeginInvoke的区别,HAHA -
精通.NET互操作 P/Invoke,C++Interop和COM Interop 【带书签目录】
2018-04-09 19:17:39《精通.NET互操作P/Invoke,C++Interop和COM Interop》介绍Windows平台上的托管代码与非托管代码之间进行互操作的各种技术,包括由.NET提供的各种互操作方法、属性以及各种工具的用法及其工作原理。《精通.NET互操作... -
invoke回调
2013-11-25 16:05:16讲解java的相关设计,回调的方法,一些思想。dengdeng zzzzz -
invoke.hpp:小时
2021-05-28 20:05:04invoke.hpp std :: invoke / std :: apply for C ++ 11/14 要求 > = 4.9 > = 3.8 > = 2015年 安装 是仅标头的库。 您需要做的就是将headers文件从headers目录复制到您的项目中,并包括它们: # include " ... -
Invoke-Mimikatz.txt
2019-03-01 13:32:26Invoke-Mimikatz -
Invoke反射
2014-07-30 11:23:41本程序实现Invoke反射,内置说明txt文档,可以参考 -
WinForm下多线程配合Invoke函数ping百度实例。
2014-09-05 14:13:14WinForm下多线程配合Invoke函数ping百度和主线程ping百度对比。很容易就可以看出俩者的区别。 -
C#中Invoke的用法
2021-01-22 14:47:28一直对invoke和begininvoke的使用和概念比较混乱,这两天看了些资料,对这两个的用法和原理有了些新的认识和理解。 首先说下,invoke和begininvoke的使用有两种情况: 1. control中的invoke、begininvoke。 2. ...一直对invoke和begininvoke的使用和概念比较混乱,这两天看了些资料,对这两个的用法和原理有了些新的认识和理解。
首先说下,invoke和begininvoke的使用有两种情况:
1. control中的invoke、begininvoke。
2. delegrate中的invoke、begininvoke。
这两种情况是不同的,我们这里要讲的是第1种。下面我们在来说下.NET中对invoke和begininvoke的官方定义。
control.invoke(参数delegate)方法:在拥有此控件的基础窗口句柄的线程上执行指定的委托。
control.begininvoke(参数delegate)方法:在创建控件的基础句柄所在线程上异步执行指定委托。
根据这两个概念我们大致理解invoke表是同步、begininvoke表示异步。
如果你的后台线程在更新一个UI控件的状态后不需要等待,而是要继续往下处理,那么你就应该使用BeginInvoke来进行异步处理。
如果你的后台线程需要操作UI控件,并且需要等到该操作执行完毕才能继续执行,那么你就应该使用Invoke。
我们来做一个测试。
invoke 例子:
private void button1_Click(object sender, EventArgs e) { MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString()+"AAA"); invokeThread = new Thread(new ThreadStart(StartMethod)); invokeThread.Start(); string a = string.Empty; for (int i = 0; i < 3; i++) //调整循环次数,看的会更清楚 { Thread.Sleep(1000); a = a + "B"; } MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString()+a); } private void StartMethod() { MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString()+"CCC"); button1.Invoke(new invokeDelegate(invokeMethod)); MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString()+"DDD"); } private void invokeMethod() { //Thread.Sleep(3000); MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "EEE"); }
结论:我们运行后,看下程序的运行顺序,1AAA->3CCC和1BBB->1EEE ->3DDD 。
解释:主线程运行1AAA,然后1BBB和子线程3CCC同时执行,然后通过invoke来将invokemethod方法提交给主线程,然后子线 程等待主线程执行,直到主线程将invokemethod方法执行完成(期间必须等待主线程的任务执行完成,才会去执行invoke提交的任务),最后执 行子线程3DDD。
begininvoke 例子:
private void button1_Click(object sender, EventArgs e) { MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString()+"AAA"); invokeThread = new Thread(new ThreadStart(StartMethod)); invokeThread.Start(); string a = string.Empty; for (int i = 0; i < 3; i++) //调整循环次数,看的会更清楚 { Thread.Sleep(1000); a = a + "B"; } MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString()+a); } private void StartMethod() { MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString()+"CCC"); button1.BeginInvoke(new invokeDelegate(invokeMethod)); MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString()+"DDD"); } private void beginInvokeMethod() { //Thread.Sleep(3000); MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "EEEEEEEEEEEE"); }
结论: 我们运行后看看执行的结果:1AAA->1BBB和3CCC->1EEE和3DDD。
解释: 主线程运行1AAA,然后1BBB和子线程3CCC同时执行,然后通过begininvoke来将invokemethod方法提交给主线程,然后主线程执行1EEE(主线程自己的任务执行完成), 同时子线程继续执行3DDD。
通过这个两段代码的测试比较,我们会发现其实invoke和begininvoke所提交的委托方法都是在主线程中执行的,其实根据我invoke 和begininvoke的定义我们要在子线程中来看这个问题,在invoke例子中我们会发现invoke所提交的委托方法执行完成后,才能继续执行 DDD;在begininvoke例子中我们会发现begininvoke所提交的委托方法后,子线程讲继续执行DDD,不需要等待委托方法的完成。 那么现在我们在回想下invoke(同步)和begininvoke(异步)的概念,其实它们所说的意思是相对于子线程而言的,其实对于控件的调用总是由 主线程来执行的。我们很多人搞不清这个同步和异步,主要还是因为我们把参照物选错了。其实有时候光看概念是很容易理解错误的。
解决从不是创建控件的线程访问它
在多线程编程中,我们经常要在工作线程中去更新界面显示,而在多线程中直接调用界面控件的方法是错误的做法,Invoke 和 BeginInvoke 就是为了解决这个问题而出现的,使你在多线程中安全的更新界面显示。
正确的做法是将工作线程中涉及更新界面的代码封装为一个方法,通过 Invoke 或者 BeginInvoke 去调用,两者的区别就是一个导致工作线程等待,而另外一个则不会。
而所谓的“一面响应操作,一面添加节点”永远只能是相对的,使 UI 线程的负担不至于太大而已,因为界面的正确更新始终要通过 UI 线程去做,我们要做的事情是在工作线程中包揽大部分的运算,而将对纯粹的界面更新放到 UI 线程中去做,这样也就达到了减轻 UI 线程负担的目的了。
举个简单例子说明下使用方法,比如你在启动一个线程,在线程的方法中想更新窗体中的一个TextBox..
using System.Threading;
//启动一个线程
Thread thread=new Thread(new ThreadStart(DoWork));
thread.Start();
//线程方法
private void DoWork()
{
this.TextBox1.Text="我是一个文本框";
}
如果你像上面操作,在VS2005或2008里是会有异常的...
正确的做法是用Invoke\BeginInvoke
using System.Threading;
namespace test
{
public partial class Form1 : Form
{
public delegate void MyInvoke(string str1,string str2);
public Form1()
{
InitializeComponent();
}
public void DoWork()
{
MyInvoke mi = new MyInvoke(UpdateForm);
this.BeginInvoke(mi, new Object[] {"我是文本框","haha"});
}
public void UpdateForm(string param1,string parm2)
{
this.textBox1.Text = param1+parm2;
}
private void button1_Click(object sender, EventArgs e)
{
Thread thread = new Thread(new ThreadStart(DoWork));
thread.Start();
}
}
}
注意代理的使用!后面再次补充
在 WinForm开发过程中经常会用到线程,有时候还往往需要在线程中访问线程外的控件,比如:设置textbox的Text属性等等。如果直接设置程序必 定会报出:从不是创建控件的线程访问它,这个异常。通常我们可以采用两种方法来解决。一是通过设置control的属性。二是通过delegate,而通 过delegate也有两种方式,一种是常用的方式,另一种就是匿名方式。下面分别加以说明.
首先,通过设置control的一个属性值为false.我们可以在Form_Load方法中添加:Control.CheckForIllegalCrossThreadCalls=false;来解决。设置为false表示不对错误线程的调用进行捕获。这样在线程中对textbox的Text属性进行设置时就不会再报错了。
其次,通过delegate的方法来解决。
普通的委托方法例如:delegate void SafeSetText(string strMsg); private void SetText(string strMsg) { if(textbox1.InvokeRequired) { SafeSetText objSet=new SafeSetText(SetText); textbox1.Invoke(objSet,new object[]{strMsg}); } else { textbox1.Text=strMsg; } }
在线程内需要设置textbox的值时调用SetText方法既可。我们还可以采用另一种委托的方式来实现,那就是匿名代理,例如:
delegate void SafeSetText(string strMsg); private void SetText2(string strMsg) { SafeSetText objSet = delegate(string str) { textBox1.Text = str; } textBox1.Invoke(objSet,new object[]{strMsg}); }
这样同样可以实现。
个人觉得还是采用代理好些。在C# 3.0及以后的版本中有了Lamda表达式,像上面这种匿名委托有了更简洁的写法。.NET Framework 3.5及以后版本更能用Action封装方法。例如以下写法可以看上去非常简洁:
void ButtonOnClick(object sender,EventArgs e)
{
this.Invoke(new Action(()=>
{
button.Text="关闭";
}));
}
最新:
Invoke(() =>
{button.Text="关闭";
});