GTK中GTask与主循环的使用疑问及代码调试求助
GTK GTask相关技术疑问解答
我的疑问
我是GTK新手,正在探索GTask的工作原理,现有以下技术疑问:
- 如何在主线程中实现程序退出前等待所有任务完成且回调函数执行完毕?
- 调用
g_task_run_in_thread后,是否必须每次都调用g_main_loop_run,并在任务回调中调用g_main_loop_quit? g_main_loop_run是否会阻塞线程直到调用g_main_loop_quit?- 在单个主循环中运行多个任务的最佳方式是什么?
测试代码
#include <iostream> #include <gtk/gtk.h> #include <gio/gio.h> typedef GObject myGObj; static volatile int flag = 0; GMainLoop *loop; static int again = 0; static void some_blocking_function_async (GTask *task, gpointer object, gpointer task_data, GCancellable *cancellable) { printf("%p: %s\n", g_thread_self(), __func__); std::cout << " some_blocking_function_async sleep\n"; usleep(3000000); //emulate some work std::cout << " some_blocking_function_async waked up\n"; //g_main_loop_quit(loop); //callback no called if loop quited here g_task_return_boolean(task, TRUE); } static void cb_func(GObject *gobject, GAsyncResult *res, gpointer user_data) { printf("%p: %s\n", g_thread_self(), __func__); std::cout << "cb_func sleep\n"; usleep(1000000); std::cout << "cb_func waked up\n"; flag++; GTask *task = (GTask*)user_data; g_main_loop_quit(loop); //task don't call a calback if main loop not quited and runned again ??? if(flag == 1 && again == 0){ again++; std::cout << "Run 2nd Task\n"; GTask* task2 = g_task_new(NULL, NULL, cb_func, NULL); g_task_run_in_thread(task2, some_blocking_function_async); g_object_unref(task2); g_main_loop_run(loop); //task don't call a calback if main loop not quited and runned again ??? } } int main(int, char**){ printf("%p: %s\n", g_thread_self(), __func__); std::cout << "Hello, from testGt!\n"; loop = g_main_loop_new(NULL, FALSE); GTask* task = g_task_new(NULL, NULL, cb_func, NULL); g_task_run_in_thread(task, some_blocking_function_async); std::cout << "Task runned, from testGt!\n"; g_object_unref(task); g_main_loop_run(loop); std::cout << "Loop runned, from testGt!\n"; while(flag != 2){ std::cout << "Sleep, from testGt!\n"; usleep(1000000); std::cout << "Wakeup, from testGt!\n"; } g_main_loop_quit(loop); std::cout << flag << " Bye, from testGt!\n"; return 0; }
程序输出
0x564375b85c00: main Hello, from testGt! Task runned, from testGt! 0x564375b68b60: some_blocking_function_async some_blocking_function_async sleep some_blocking_function_async waked up 0x564375b85c00: cb_func cb_func sleep cb_func waked up Run 2nd Task 0x564375b68b60: some_blocking_function_async some_blocking_function_async sleep some_blocking_function_async waked up 0x564375b85c00: cb_func cb_func sleep cb_func waked up Loop runned, from testGt! 2 Bye, from testGt!
CMakeLists.txt
cmake_minimum_required(VERSION 3.0.0) project(testGt VERSION 0.1.0 LANGUAGES C CXX) include(CTest) enable_testing() FIND_PACKAGE(PkgConfig REQUIRED) PKG_CHECK_MODULES(GTK REQUIRED gtk+-3.0) INCLUDE_DIRECTORIES(${GTK_INCLUDE_DIRS}) add_executable(testGt main.cpp) TARGET_LINK_LIBRARIES(testGt ${CMAKE_THREAD_LIBS_INIT} ${GTK_LIBRARIES}) set(CPACK_PROJECT_NAME ${PROJECT_NAME}) set(CPACK_PROJECT_VERSION ${PROJECT_VERSION}) include(CPack)
问题解答
1. 如何在主线程中实现程序退出前等待所有任务完成且回调函数执行完毕?
通过原子计数器+主循环轮询的方式实现:
- 维护一个原子计数器,每创建一个任务就将计数器加1,任务回调执行完毕后将计数器减1。
- 程序退出前,检查计数器是否为0:如果不为0,调用
g_main_context_iteration轮询处理事件,直到计数器归0;如果为0,直接退出。
示例思路:
static volatile int task_count = 0; // 创建任务时 task_count++; GTask* task = g_task_new(NULL, NULL, cb_func, NULL); g_task_run_in_thread(task, some_blocking_function_async); g_object_unref(task); // 回调函数中 static void cb_func(...) { // 处理业务逻辑 task_count--; } // 主函数退出前 while (task_count > 0) { g_main_context_iteration(NULL, TRUE); }
2. 调用g_task_run_in_thread后,是否必须每次都调用g_main_loop_run,并在任务回调中调用g_main_loop_quit?
不需要。g_task_run_in_thread会将任务放入GLib线程池执行,任务完成后,回调会被自动调度到默认主上下文中执行。只要主上下文处于运行状态(比如GTK应用的gtk_main()已经启动主循环),回调就会被自动处理。
只有当主线程没有运行任何主循环时,才需要调用g_main_loop_run启动事件循环,让回调有机会执行。
3. g_main_loop_run是否会阻塞线程直到调用g_main_loop_quit?
是的。g_main_loop_run会进入事件循环,持续处理队列中的事件(包括GTask回调、UI事件等),直到调用g_main_loop_quit才会退出,期间会阻塞调用它的线程。
注意:可以多次调用g_main_loop_run,每次调用都会重新进入循环,直到下一次g_main_loop_quit被触发。
4. 在单个主循环中运行多个任务的最佳方式是什么?
直接创建多个GTask并调用g_task_run_in_thread即可,无需额外操作:
- GTask会自动利用GLib线程池管理后台线程,无需手动创建和管理线程。
- 所有任务的回调都会被调度到同一个主上下文(主循环)中执行,回调本身在主线程运行,无需额外处理线程同步。
- 如果需要跟踪所有任务的完成状态,可以使用原子计数器,当计数器达到任务总数时退出主循环。
优化示例:
static volatile int completed_tasks = 0; static const int TOTAL_TASKS = 3; GMainLoop *loop; static void some_blocking_func(GTask *task, gpointer object, gpointer task_data, GCancellable *cancellable) { // 模拟阻塞工作 usleep(2000000); g_task_return_boolean(task, TRUE); } static void task_cb(GObject *obj, GAsyncResult *res, gpointer data) { completed_tasks++; if (completed_tasks == TOTAL_TASKS) { g_main_loop_quit(loop); } } int main() { loop = g_main_loop_new(NULL, FALSE); // 批量创建任务 for (int i = 0; i < TOTAL_TASKS; i++) { GTask* task = g_task_new(NULL, NULL, task_cb, NULL); g_task_run_in_thread(task, some_blocking_func); g_object_unref(task); } // 启动主循环,等待所有任务完成 g_main_loop_run(loop); g_main_loop_unref(loop); return 0; }
内容的提问来源于stack exchange,提问作者Dmitry Groker
相关产品推荐
相关产品推荐

