c++gtkglib

Gtk main loop and GTask


I am newer in GTK and now try to find a explonation of how GTask is working.

  1. My main question is how in main thread I can implement a waiting all tasks finished and callbacks called before exit from app?

  2. Is it nessesary to call g_main_loop_run after g_task_run_in_thread and call g_main_loop_quit in task callback in each time or not?

  3. Is g_main_loop_run block a thread until g_main_loop_quit called?

  4. What is the best way to run many task in one main loop?

Thank you, guys!

My code for test how it is working

#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;
}

Output

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)

Solution

  • I am not really a glib/gio expert but, I may be able to give some tips.

    1. You have a few options there, you could call g_task_get_completed on the tasks you spawn to check if they are done but, as you are spawning them from the callback, you'd need to accumulate the tasks spawned in a mutex protected vector for example.

    2. You only need to call g_main_loop_run if you want your app to run indefinitely until quit is called otherwise, if you have your own loop already, you need to "drive" glib's loop with g_main_context_iteration.

    3. Yes, g_main_loop_run will always block

    4. You can create the tasks in a loop and glib will take care of driving them.

    Here is a changed version of your code that drives glib's loop inside the while statement

    --- old.cpp 2023-07-20 09:34:13
    +++ a.cpp   2023-07-20 09:34:55
    @@ -4,7 +4,6 @@
     
     typedef GObject myGObj;
     static volatile int flag = 0;
    -GMainLoop *loop;
     static int again = 0;
     
     static void
    \ No newline at end of file
    @@ -32,10 +31,7 @@
         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++;
    \ No newline at end of file
    @@ -43,8 +39,6 @@
             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  ???
         }
     }
     
    \ No newline at end of file
    @@ -53,25 +47,24 @@
         printf("%p: %s\n", g_thread_self(), __func__);
         std::cout << "Hello, from testGt!\n";
     
    -    loop = g_main_loop_new(NULL, FALSE);
    +    auto loop = g_main_loop_new(NULL, FALSE);
    +    auto context = g_main_loop_get_context(loop);
     
         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";
    +        g_main_context_iteration(context, FALSE);
             usleep(1000000);
             std::cout << "Wakeup, from testGt!\n";
         }
     
    -    g_main_loop_quit(loop);
    -
         std::cout << flag << " Bye, from testGt!\n";
     
         return 0;
    \ No newline at end of file
    

    On this code, you will see that it drives the tasks every time g_main_context_iteration is called, and when you achieve the desired number of ran tasks, it will just move out and finish the program.