您的位置:首页 > 产品设计 > UI/UE

/LGC设计模式/GUI 主循环设计及其在 MiniGUI, GTK, QT 的实现

2010-12-27 16:30 423 查看
GUI 主循环设计及其在 MiniGUI, GTK, QT 的实现

作者:
刘鹏

日期:

2009-05-22

介绍了 GUI 主循环的设计思路,并分析了 MiniGUI, GTK, QT 的主循环。

简介

我们知道GUI应用程序都是事件驱动的。这些事件大部分都来自于用户,比如键盘事件、鼠标事件或笔点事件。还有一些事件来自于系统内部,比如定时事件、socket事件和其它文件事件等等。在没有任何事件的情况下,应用程序处于睡眠状态。1

因为这种事件驱动机制,GUI应用程序都毫无例外的需要一个主循环(main loop)。
主循环(main loop)控制应用程序什么时候进入睡眠状态,什么时候被唤醒。主
循环实现得好,应用程序才能工作正常又省电。1

目前常见的主循环设计主要是三种思路:

消息队列 + 信号量(semaphore) + sem_wait;

事件源 + select;

事件源 + poll。

对于第一种设计思路,主循环不断的从消息队列中提取消息分发给窗口,如果消
息队列中没有消息,主循环调用 sem_wait 进入休眠状态,直到有信息出现被唤
醒。MiniGUI
的主循环就是这样实现的。

对于第二种思路,主循环使用 poll 函数监听事件源,若有事件出现则进行处理,
否则进入休眠状态,直到有事件到来。GTK
+ 的主循环就是采用的这种思路。

第三种思路与第二种思路类似,不同之处是主循环使用 select 函数监听事件源,
QT
的主循环就是采用的这种思路。

下面结合具体的实现逐一分析这三种思路。

MiniGUI
的主循环

MiniGUI
应用程序的主循环与 Win32 类似,大致如下:

while (GetMessage (&Msg, hMainWnd)) {

DispatchMessage (&Msg);

}

MiniGUI
的主循环中,它不断的从消息队列中提取消息,然后分发给消息的
目标(通常是窗口),直到GetMessage返回FALSE(收到WM_QUIT消息,一般调用
PostQuitMessage)为止,如果队列中没有消息,应用程序就进入睡眠状态。

这种方法的优点是简单明了,但缺陷也是明显的,它只能挂在消息队列上,而不
能同时挂在多个事件源上(如管道和socket等)。要挂在多个事件源上,需要使用
其它方式,比如用WaitForMultipleObjects,那就比较麻烦了。

关于主循环的休眠,MiniGUI
采用的是信号量的方式(sem_wait)。我们进入
GetMessage 接口的实现:

...

checkagain:

LOCK_MSGQ (pMsgQueue);

... peek message ...

UNLOCK_MSGQ (pMsgQueue);

if (bWait) {

/* no message, wait again. */

sem_wait (&pMsgQueue->wait);

goto checkagain;

}

GetMessage 调用 sem_wait 函数等待消息队列,若没有消息主循环进入休眠等待状态。

GTK
+ 的主循环1

GTK
+应用程序中,其主循环(main loop)更加简单,但是非常的不明了。
每个 gtk 应用程序都有下面这行代码:

gtk_main ();

不少人用GTK
+写了很长时间的程序,还是觉得这行代码很神秘,不知道里面到底
干了什么。我们在这里分析下 gtk_main 的工作原理。

gtk_main 主要是对 glib 的 main loop 的包装,基本分为三步:

调用初始化函数;

进入glib main loop;

调用~初始化函数。

所以弄清楚glib main loop之后,gtk_main的实现也就尽收眼底了,这里重点分
析glib的main loop的实现。main loop使用模式大致如下:

loop = g_main_loop_new (NULL, TRUE);

g_main_loop_run (loop);

g_main_loop_quit ();

g_main_loop_new创建一个main loop对象,一个main loop对象只能被一个线程
使用,但一个线程可以有多个main loop对象。在GTK
+应用中,一个线程使用多
个main loop的主要用途是实现模态对话框,它在gtk_dialog_run函数里创建一
个新的main loop,通过该main loop分发消息,直到对话框关闭为止。

g_main_loop_run则是进入主循环,它会一直阻塞在这里,直到让它退出为止。
有事件时,它就处理事件,没事件时就睡眠。

g_main_loop_quit则是用于退出主循环,相当于Win32下的PostQuitMessage函数。

Glib main loop 的最大特点就是支持多事件源,使用非常方便。来自用户的键盘
和鼠标事件、来自系统的定时事件和socket事件等等,还支持一个称为idle的事
件源,其主要用途是实现异步事件。

Glib Main loop的设计思想是基于 poll 的 "prepare/check/dispatch" 模式,
它的基本组成如下图所示:



来自资料1
GMainLoop的主要部件是GMainContext,GMainContext可以在多个GMainLoop间共
享,但要求这些GMainLoop都在同一个线程中运行,前面提到的模态对话框就属
于这一类。GMainContext通常由多个GSource组成,GSource是事件源的抽象,任
何事件源,只要实现GSource规定的接口,都可以挂到GMainContext中来。

GSource的接口函数有:

gboolean (*prepare) (GSource *source, gint *timeout_);进入睡眠之前,
在g_main_context_prepare里,mainloop调用所有Source的prepare函数,
计算最小的timeout时间,该时间决定下一次睡眠的时间。

gboolean (*check) (GSource *source); poll被唤醒后,在
g_main_context_check里,mainloop调用所有Source的check函数,检查是
否有Source已经准备好了。如果poll是由于错误或者超时等原因唤醒的,就
不必进行dispatch了。

gboolean (*dispatch) (GSource*source, GSourceFunc
callback,gpointer user_data); 当有Source准备好了,在
g_main_context_dispatch里,mainloop调用所有Source的dispatch函数,
去分发消息。

void (*finalize) (GSource *source); 在Source被移出时,mainloop调用
该函数去销毁Source。

Main loop的工作流程简图如下:



来自资料1
下面我们看看几个内置Source的实现机制:

Idle 它主要用实现异步事件,功能类似于Win32下的PostMessage。但它还支持
重复执行的特性,根据用户注册的回调函数的返回值而定。

g_idle_prepare把超时设置为0,也就是即时唤醒,不进入睡眠状态。

g_idle_check 始终返回TRUE,表示准备好了。

g_idle_dispatch 调用用户注册的回调函数。

Timeout 它主要用于实现定时器,支持一次定时和重复定时,根据用户注册的回调函数的返回值而定。

g_timeout_prepare 计算下一次的超时时间。

g_timeout_check 检查超时时间是否到了,如果到了就返回TRUE,否则返回FALSE。

g_timeout_dispatch调用用户注册的回调函数。

线程可以向自己的mainloop中增加Source,也可以向其它线程的mainloop增加
Source。向自己的mainloop中增加Source时,mainloop已经唤醒了,所以不会存
在什么问题。而向其它线程的mainloop增加Source时,对方线程可能正挂在poll
里睡眠,所以要想法唤醒它,否则Source可能来不及处理。在Linux下,这是通
过wake_up_pipe管道实现的,mainloop在poll时,它除了等待所有的Source外,
还会等待wake_up_pipe管道。要唤醒poll,调用
g_main_context_wakeup_unlocked向wake_up_pipe里写入字母A就行了。

QT
的主循环

QT
采用的是基于 select 的主循环。主循环使用 select 函数监听事件源,当
有事件发生时对其进行处理,若没有事件则休眠等待。

QT
的事件处理模块如下图所示:



类 QAbstractEventDispatcher 封装了所有的事件处理接口,包括事件的处理
(processEvents),Socket 的登记和注销 (xxxSocketNotifier),Timer 的注册
与注销(xxxTimer)等。

不同版本的 QT
代码实现不同的 QAbstractEventDispathcer 子类。这里,我分
析了 X11 平台的 QT
实现,它实现了 QEventDispathcerUnix 类,并派生出
QEventDispatcherX11 类。

processEvents 方法负责处理事件;select 方法提供了监听事件的接口,它实际上去调系统的 select 函数。

QT
相关代码在 src/gui/kernel 和 src/corelib/kernel 目录下。

Reference

GTK+主循环(main loop)的工作原理

Common main loop: Qt ported to glib main loop (experimental)

GTK编程——Gtk+的主循环
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: