invoke begininvoke

来源:互联网 发布:单片机 控制 四轴 编辑:程序博客网 时间:2024/05/17 20:01
 
 windows程序设计是一种事件驱动方式的程序设计模式,主要是基于消息的.当用户在键盘上按下一个A字时,操作系统感应到这一个动作,并且知道是按下A键,但操作系统不作出反应只是把这一事件传给应用程序(如记事本),由应用程序去决定对这一个事件做出反应.那么操作系统是如何将这一事件传给应用程序的呢?是通过消息机制来实现的.操作系统将每个事件都包装成一个消息结构体MSG来传递给应用程序.在windows API函数库中有一个MSG的结构体函数.其定义如下:
typedef struct tagMSG
{
    HWND hwnd;
    UINT   message;
    WPARAM wParam;
    LPARAM  lParam;
    DWORD  time;
    POINT pt;
}MSG;
在以后的应用中用MSG来代替struct tagMsg。
该结构体中各成员变量的作用如下:
第一个成员中:HWND是一个数据类型,是用来标识窗口句柄的.hwnd的值是代表当前要打开的窗口.
第二个成员中:UINT的原型是unsigned int, message的值是用数值,不同的数值表示不同的消息.但数字的表述性不明显,故用宏定义来取代数值.其宏定义形式为WM_XXX(WM是Window Message的缩写),在程序中通常是以WM_XXX的形式来使用消息的.
第三四个成员中:LPARAM的数据原型是unsigned int;LPARAM的数据原型是long int. wParam与lParam是用来表示消息的附加信息.例如:当输入一个字符时,message变量的值就是WM_CHAR,但是到底输入的是哪一个字符时就由wParam与lParam来确定.wParam与lParam表示的信息随消息的不同而不同.
第五个成员中:DWORD的数据原型是unsigned long,time的值表示消息投递到消息队列的时间.
第六个成员中:表示的是鼠标当前的位置.

流行的 GUI 编程都有一个重要的概念与之相关,即"事件驱动编程"。
事件驱动的含义就是,程序的流程不再是只有一个入口和若干个出口的串行执行线路;
相反,程序会一直处于一个循环状态,在这个循环当中,程序从外部输入设备获取某些事件,
比如用户的按键或者鼠标的移动,然后根据这些事件作出某种的响应,并完成一定的功能,
这个循环直到程序接受到某个消息为止。"事件驱动"的底层设施,就是常说的"消息队列"和"消息循环"。
一旦窗口建立之后,窗口就会从消息队列当中获取属于自己的消息,然后交由它的窗口过程进行处理。

多消息队列 每个创建窗口的线程拥有独立的消息队列 只有一个消息队列。
所有窗口共享一个消息队列。除非嵌套消息循环,否则一个程序中只有一个消息循环。
消息循环就是一个循环体,在这个循环体中,程序利用 GetMessage 函数不停地从消息队列中获得消息,
然后利用 DispatchMessage 函数将消息发送到指定的窗口,也就是调用指定窗口的窗口过程,并传递消息及其参数。
典型的消息循环如下所示:
  
  while (GetMessage (&Msg, hMainWnd)) {
   TranslateMessage (&Msg);
   DispatchMessage (&Msg);
  }
GetMessage 函数从 hMainWnd 窗口所属的消息队列当中获得消息,
然后调用 TranslateMessage 函数将 MSG_KEYDOWN 和 MSG_KEYUP 消息翻译成 MSG_CHAR 消息,
最后调用 DispatchMessage 函数将消息发送到指定的窗口。

因为它会抛出一个System.InvalidOperationException异常,
异常描述就是“线程间操作无效: 从不是创建控件‘progressBar’的线程访问它。”

CheckForIllegalCrossThreadCalls属性是否允许在创建UI的线程之外访问线程; true 为不允许,false为允许   

因为在.NET中做了限制,不允许在调试环境下使用线程访问并非它自己创建的UI控件,
这么做可能是怕在多线程环境下对界面控件进行操作会出现不可预知的情况,
如果从另外一个线程操作windows窗体上的控件,就会和主线程产生竞争,造成不可预料的结果,甚至死锁。

如果开发者可以确认自己的代码操作界面不会出现问题,可以用比较简单的方法解决,
那就是设置CheckForIllegalCrossThreadCalls这个静态属性,
它默认是true,如果将其设为false的话,以后在多线程环境下操作界面也不会抛出异常了
invoke表是同步、begininvoke表示异步
对于异步调用,.NET内部究竟做了什么? 
一旦你使用.NET完成了一次异步调用,它都需要一个线程来处理异步工作内容(以下简称异步线程),异步线程不可能是当前的调用线程,因为那样仍然会造成调用线程的阻塞,与同步无异。事实上,.NET会将所有的异步请求队列加入线程池,以线程池内的线程处理所有的异步请求。对于线程池似乎不必了解的过于深入,但我们仍需要关注以下几点内容: 
● Sleep()的异步调用会在一个单独的线程内执行,这个线程来自于.NET线程池。 
● .NET线程池默认包含25个线程,你可以改变这个值的上限,每次异步调用都会使用其中某
个线程执行,但我们并不能控制具体使用哪一个线程。 
● 线程池具备最大线程数目上限,一旦所有的线程都处于忙碌状态,那么新的异步调用将会被
置于等待队列,直到线程池产生了新的可用线程,因此对于大量异步请求,我们有必要关注请求数量,否则可能造成性能上的影响。

如果在UI线程中直接调用Invoke和BeginInvoke,数据量偏大时,依然会造成UI的假死

Control.Invoke,Control.BeginInvoke和delegate.Invoke,delegate.BeginInvoke是不同的。 

2. Control.Invoke中的委托方法,执行在主线程,也就是我们的UI线程。

Control.BeginInvoke从命名上来看虽然具有异步调用的特征(Begin),但也仍然执行在UI线程。

当有多个并发线程尝试对UI进行读写时,容易造成线程争用资源带来的死锁。
当数据量偏大时,我们会发现窗体变成了空白面板。此时如果用鼠标点击,
窗体标题将会出现”失去响应”的字样,而实际上UI线程仍在工作着,这对用户来说是一种极度糟糕的体验。
如果你希望了解其中的原因(并不复杂),
并彻底解决该问题,那么花时间读完此文也许是个不错的选择。 

一般来说,窗体阻塞分为两种情况。

一种是在UI线程上调用耗时较长的操作,例如访问数据库,这种阻塞是UI线程被占用所导致,可以通过delegate.BeginInvoke的异步编程解决;

另一种是窗体加载大批量数据,例如向ListView、DataGridView等控件中添加大量的数据



0 0
原创粉丝点击