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 一节记录了库作者应该避免的几个缺点。