浅析SWT事件模型

来源:互联网 发布:霍华德巅峰数据 编辑:程序博客网 时间:2024/05/19 13:56

事件(event)

In computing, an event is an action that is usually initiated outside the scope of a program and that is handled by a piece of code inside the program. Typically events are handled synchronous with the program flow, that is, the program has one or more dedicated places where events are handled. Typical sources of events include the user (who presses a key on the keyboard, in other words, through a keystroke). Another source is a hardware device such as a timer. A computer program that changes its behavior in response to events is said to be event-driven, often with the goal of being interactive.

通俗来讲,事件就是告诉应用程序某些事情发生了,你来给处理一下。事件驱动模型是以Observer Pattern建立起来的,那么就需要一个Observer和Handler。

SWT事件模型的Observer - EventTable

在SWT中Observer在Widget中实现。Widget中持有EventTable对象的引用。EventTable对象主要用于保存事件以及事件的Handler,即Listener对象。EventTable的结构图如图所示

由上图可见,EventTable包含两个主要的成员变量:types和listeners分别用于存放事件值和对应的listener。可以简单的理解为它是一个map<int, Listener>的实现。它提供了Observer模式中的增加/删除observer(对应于hook/unhook)以及NotifyObserver的(sendEvent)方法。types和listeners是以4为单位动态增长的数组,为什么选择4,个人理解原因有两个:一是内存对齐,每次增长4的话总能对齐到合理的内存位置上。二对应一个鼠标点击事件要添加3个Listeners,所以最小的增长单位应该为3,再考虑一,所以选择4。

对于hook/unhook就是我们常用的数组赋值和清除的操作,不用过多解释,而对于sendEvent这个方法最核心的代码是找到事件处理对象,然后调用listener.handleEvent(event)。这里要提一下的是,我开始看这段代码的时候很奇怪为什么事件处理机制中用到的是简单的两个数组,每次查找对应的事件的时候都要从数组的开始处遍历到数组的末尾,算法复杂度是O(n),如果采用更合理的hash结构,算法复杂度变成O(1)岂不是更加快速。如果有成千上百的事件处理加入进来,采用hash表的方法的确可以提高速度。这里不采用的原因很简单:widget是公共父类,每个继承自它的子类都会有父类的一份拷贝,也就是说EventTable会在每个UI control中有一份。我开始以为所有的事件以及事件回调都会放在一起管理,这样对于一个大的应用程序,那么有成百上千的事件处理Listener也不奇怪。而事实是每个控件维护自己的观察者。所以对于每个控件来说,它的观察者就比较有限,每次从头遍历数组的方法效率会比较高效。(对于小数据量来说hash的hash码计算代价+空间占用成本>>数组的成本)。

EventTable中另外一个值得关注的变量就是level。level是为了保证多线程并发访问EventTable的线程安全性的。在源代码中没有level的注释,对于这种代码我是比较感兴趣的,因为可能是涉及到的一些核心代码或者tricks。查看level的Call Hirerachy可以发现有两处调用了它: sendEvent和remove。

level如果不等于0,说明正在调用事件处理;如果level<0,说明types和listeners数组需要清理。先记住这两点,然后结合代码去分析这个结论的原因。


public void sendEvent (Event event) {if (types == null) return; level += level >= 0 ? 1 : -1; //设置level,正在进行事件处理try {for (int i=0; i<types.length; i++) {if (event.type == SWT.None) return; //为什么不再外面判断,不明白。if (types [i] == event.type) {Listener listener = listeners [i];if (listener != null) listener.handleEvent (event);}}} finally {boolean compact = level < 0;level -= level >= 0 ? 1 : -1;if (compact && level == 0) { //需要清理并且是最后一个事件处理线程,对数组进行整理。int index = 0;for (int i=0; i<types.length; i++) {if (types [i] != 0) {types [index] = types [i];listeners [index] = listeners [i];index++;}}for (int i=index; i<types.length; i++) {types [i] = 0;listeners [i] = null;}}}}
void remove (int index) {if (level == 0) { // 如果没在调用事件处理,直接进行内存覆盖。int end = types.length - 1;System.arraycopy (types, index + 1, types, index, end - index);System.arraycopy (listeners, index + 1, listeners, index, end - index);index = end;} else { // 否则把level取反,然后把相应的事件设置为空,最后让sendEvent进行数组整理。if (level > 0) level = -level;}types [index] = 0;listeners [index] = null;}

上面的代码已经写到了注释中,不过为了清晰,还是再描述一遍。对于没有冲突的不再叙述。举个冲突的例子,开始时level = 0, 如果有个事件event1发生了,这个时候调用sendEvent(event1), 会把level设置成level = 1; 在事件处理的过程中(假设用的时间比较长),这个时候另外一个线程调用了unhook(eventx),那么最终在remove()中会把level取反,然后把eventx置空。等事件event1处理完成后,发现level=-1,所以需要整理两个数组,它会判断一下level=0,保证是最后一个返回的线程来整理一次。对于更复杂的情形同样也可以理解。

由上可知,level的作用是用于线程安全。但是理论上level并不是强线程安全的。原因很简单:当level=0时,会有有可能同时存在两个线程,一个在调用remove()中的内存覆盖代码,另外一个在调用sendEvent中的数组整理,这时会有几率出现事件与事件处理函数失配的情况:

                     当sendEvent线程在判断到types[i]!= 0时,它调用了 types[index] = types[i]; 如果这时该线程时间片用完,并且进入了remove线程,正好把i所在位置的事件处理函数给清掉这时types[i]其实被types[i+1]给覆盖掉,那么再回到sendEvent线程时就可能产生listeners[index] = listeners[i],那么就会产生失配现象。这种几率有多大我想应该是微乎及微的。某天弹出莫名的bug时,说不定就是它造成的。


SWT事件模型的Handler - listener

观察者模式中的handler在SWT中既是所有实现了Listener接口的类。接口比较简单,只有一个void handleEvent (Event event),不再叙述。

SWT中的事件来源

事件的观察者和处理者已经明了了,那么所有的事件是怎么来的呢。简单来说swt中的事件是由swt转义的系统消息。对于windows系统来说,就是MSG。具体可以查看Widget中的以wm开头的方法,比如wmRButtonDown()把鼠标右键按下消息发送到Display的eventQueue中或者直接调用相应的Listener.
        SWT如何接受到windows的msg,可以查看Display中的init()方法,它调用Native的方法注册窗户回调函数来接受windows消息,然后再transplate和dispatch..

原创粉丝点击