Main 事件循环
Main 事件循环
Main 事件循环管理所有可用的事件源,供 GLib 和 GTK 应用程序使用。这些事件可能来自许多不同类型的来源,例如文件描述符(普通文件、管道或套接字)和超时。还可以使用 g_source_attach()
添加新类型的事件源。
为了允许在不同线程中处理多组独立的来源,每个来源都与 GMainContext
关联。GMainContext
只能在单个线程中运行,但可以从其他线程向其中添加或从中移除来源。所有在 GMainContext
或内置 GSource
上运行的函数都是线程安全的。
每个事件源都分配了一个优先级。默认优先级 G_PRIORITY_DEFAULT
为 0。小于 0 的值表示较高优先级。大于 0 的值表示较低优先级。高优先级来源的事件始终在低优先级来源的事件之前处理。
还可以添加空闲函数并分配其优先级。只要没有较高优先级的事件准备处理,就会运行这些空闲函数。
GMainLoop
数据类型表示 Main 事件循环。使用 g_main_loop_new()
创建 GMainLoop。在添加初始事件源后,将调用 g_main_loop_run()
。这会持续检查每个事件源是否有新事件,并分发这些新事件。最后,处理来自其中一个来源的事件将导致调用 g_main_loop_quit()
退出 Main 循环,并且 g_main_loop_run()
会返回。
可以递归创建 GMainLoop
的新实例。在显示模态对话框框时,经常在 GTK 应用程序中使用此方法。请注意,事件源与特定 GMainContext
关联,并且将为与该 GMainContext
关联的所有 Main 循环检查并分发事件源。
库可能包含其中一些函数的包装器,例如 gtk_main()
、gtk_main_quit()
和 gtk_events_pending()
。
创建新来源类型
GMainLoop
功能的一个不同寻常的特性是,可以创建并使用新类型的事件源,以补充内置的事件源类型。新事件源类型用于处理 GDK 事件。通过从 GSource
结构“派生”,可以创建新来源类型。派生的来源类型由一个结构表示,该结构具有以下内容:GSource
结构作为第一个元素,以及特定于新来源类型其他元素。要创建新来源类型的实例,请调用 g_source_new()
,并传入派生结构的大小以及函数表。这些 GSourceFuncs
决定新来源类型的行为。
新来源类型主要通过两种方式与 Main 上下文交互。它们在 GSourceFuncs
中的准备函数可以设置超时,以确定 Main 循环在再次检查来源之前休眠的最大时间量。此外,或同时,该来源还可以使用 g_source_add_poll()
向 Main 上下文检查的集合添加文件描述符。
自定义 Main 循环迭代
可以使用 g_main_context_iteration()
运行 GMainContext
的单个迭代。在某些情况下,所需的更详细的控制正是如何详细处理主循环工作的方式,例如,在将 GMainLoop
与外部主循环集成时。在这些情况下,你可以直接调用 g_main_context_iteration()
的组成函数。这些函数是 g_main_context_prepare()
、g_main_context_query()
、g_main_context_check()
和 g_main_context_dispatch()
。
主上下文的声明
这些函数的操作可以通过状态图表最好地看到,如本图像中显示的那样。
在 UNIX 上,GLib 主循环与 fork()
不兼容。使用主循环的任何程序都必须从子进程 exec()
或 exit()
,而不返回主循环。
源的内存管理
对于传递到要调用的 GSource
的用户数据以在调用时传递到其回调中,有两个内存管理选项。此数据在对 g_timeout_add()
、g_timeout_add_full()
、g_idle_add()
的调用中提供,更一般地,使用 g_source_set_callback()
提供。此数据通常是“拥有”超时或空闲回调的对象,例如小部件或网络协议实现。在许多情况下,如果此拥有对象被销毁后还要调用回调,则会出错,因为这会导致使用释放的内存。
第一个,也是首选的选项是存储源 ID,该源 ID 由诸如 g_timeout_add()
或 g_source_attach()
的函数返回,并使用 g_source_remove()
在拥有对象完成时明确地从主上下文中移除该源。这可确保仅在对象仍存在时才调用回调。
第二个选项是在回调中保留对对象的强引用,并在回调的 GDestroyNotify
中释放该引用。这可确保在源完成之前一直保留该对象,这保证在最后一次调用该源后才会保留该对象。GDestroyNotify
是另一个回调,传递给 GSource
函数的“完整”变量(例如,g_timeout_add_full()
)。当源完成时就会调用它,并且它被设计用于释放这样的引用。
这种第二种方法的一个重要的警告是,如果在主循环停止之前调用 GSource
,它将使对象无限期地保留,这可能是不可取的。
教程
GMainContext
很复杂,对于刚开始使用 GLib 的开发者来说尤其令人望而生畏。不幸的是,不恰当地使用 GMainContext
通常会导致难以调试的错误。 主上下文教程 为使用 GMainContext
的开发者提供了有价值的指导,强烈建议阅读。特别是,在库中使用 GMainContext 一节记录了库作者应该避免的几个缺点。