有一次我给同事讲述跨线程调用时使用了高速行驶的并行列车来比喻,感受比较形象。安全
多线程就像多个并行的列车,每一个线程在各自的轨道上不断向前行驶。主界面所在的线程称为UI线程,也叫主线程,主线程依靠消息驱动,能够将主线程的列车每节车箱想象为一个消息,每次转换并处理一个消息,处理过程当中若是有新的消息不会立刻处理而是放入一个消息队列,等下一轮处理。markdown
例如我在屏幕上点击一个按钮,操做系统将鼠标的按下抬起等消息推送到消息队列中。程序主线程的下一轮开始转换这个消息而后处理这个消息,发送给指定窗口。假设咱们在点击消息处理方法中进行一些界面更新,并调用了Invalidate,此时只是发出了消息,而后继续执行后续代码,当点击消息处理完毕后,才会从消息队列获取下一个消息处理。多线程
对于跨线程操做的Invoke,能够这么理解。就是并行列车在高速行驶中若是直接调用另外一个列车上的方法是很是危险的,咱们坐车的时候售票员老是提醒咱们不要把头和手伸出窗外是一个道理。因此,假如咱们在一个主线程以外的线程列车上想要UI线程去执行一个方法,此时咱们须要将方法包装成委托,而后经过Control.Invoke给主线程发送消息,主线程会在下一次消息处理时处理咱们的消息,由被调用的Control在UI线程执行咱们的方法。异步
若是咱们在Invoke后,还须要处理返回值,那么咱们本身所在的列车就不能继续开了,要停下列车,等主线程的列车处理完咱们的方法,返回结果,并经过消息发送回来,咱们收到返回的消息时,才继续开动列车处理后续消息。也就是使用Invoke的返回的WaitHandle的WaitOne方法等待了。操作系统
须要理解Windows的消息驱动机制。咱们知道任意时刻执行的代码必定是处于一个消息中,或者是空闲事件消息中。消息也是跨线程调用的基本机制。线程
Control.BeginInvoke是从线程池启动一个线程执行,相对主线程是异步的。Control.Invoke则是在其余线程中回到UI线程执行。但这两种方式都不是推荐的最优作法,推荐用TPL模式,就是使用Task来进行异步。须要回到主线程时用AsyncOperation,原理是同样的仍是发消息,只是AsyncOperation会发送给一个一定存在的句柄,避免线程安全问题。orm
另外一个不少人不明白的问题就是窗口句柄什么时候建立,以及OnLoad的时机。其实,Winform程序是对本地代码的包装而已,底层仍是过程式语言的API调用。过程语言经过句柄来惟一标识全部的本地资源,全部的方法都须要传入句柄 。而咱们建立的控件类其实并非真正的可见的类,翻看C++版本的代码就能够知道,其实仍是调用API来CreateWindow,此时传入的类名才是API中所指的类名,此时传入的参数在Control里使用了CreateParam结构体和CreateParam方法来实现。队列
简单的说吧,当咱们建立一个Button时,只是调用了Button的构造方法而已,并无在屏幕上可见,当咱们调用Parent的AddControl时,才会去建立句柄,此时才会触发控件的OnCreateControl,若是控件是一个UserControl才会触发OnLoad事件。Control的OnCreateControl和UserControl的OnLoad是同一个时机发生的。只有建立了句柄才会在屏幕上绘制出来,当父窗体隐藏时,全部子控件的句柄会销毁,由于不用绘制了,而再次Show时,会从新建立句柄。事件