9-Pin
9-Pin

Reputation: 436

GTK and threading - Displaying value from a thread into a widget

My objective is to read a large file. Each time I read a new line from the file, I want to display the current line number on the GTK UI.

I've read several excellent articles on SO and other places for doing this, none of which are giving me the desired sequencing of the threads. Below is an outline of my latest attempt. I launch a worker thread that uses g_idle_add to report the current line in the main loop. Unfortunately, the function called by g_idle_add runs only once and after the worker thread completes. Why does this happen?

/* Function that displays the counter in a GTK widget. Unfortunately, this thread runs AFTER proces_huge_file is complete. */
void display(gpointer data) {

    /* Display the current line number in the GTK UI */

    return FALSE;
  
}

/* Thread that reads the file line-by-line, and launches a function to display the current line number in the main loop. */
void process_huge_file(gpointer *data) {

    gint line_number = 0;
    while (getline(&csv_line, &len, fp) != -1) {

        line_number ++; /* Current line number, add it to the data structure */

        g_idle_add(G_SOURCE_FUNC(line_number_in_status_bar), data);

        /* Process the line */   
    }
    
}

void on_app_activate(GApplication *app, gpointer data) {

    /* Build the GTK UI and start running the worker thread. */

    GThread *thread = g_thread_new ("process_huge_file",  (GThreadFunc) process_huge_file, data); 

    g_thread_join (thread);  

}

Upvotes: 0

Views: 569

Answers (1)

9-Pin
9-Pin

Reputation: 436

I resolved the matter, but I'm not sure I could defend this in a code review. Someone needs to write a complete book on GTK threading. (Huge thanks to the developers who posted a full threaded example on github.)

The strategy is to launch a worker thread that reads the file, and that thread sends a status to another thread with access to the main loop. Here are the components.

Inside main, we connect a signal to a button. Clicking the button launches the function that processes the file.

int main(int argc, char *argv[]) {
    g_signal_connect(G_OBJECT(button_go), "clicked", G_CALLBACK(process_file), data_passer);
}

In the function that processes the file, we launch the worker thread.

gboolean process_file(GtkButton *button, gpointer data) {

    /* Get a pointer to the main loop. */
    gloop = g_main_loop_new(NULL, FALSE);

    /* Launch the worker thread.*/
    GThread *process_g_thread = g_thread_new("process_thread", (GThreadFunc)process_thread, gloop);

    /* Continue running the main loop while the worker is running. */
    g_main_loop_run(gloop);

    /* Halt further processing in the main loop at this point until the worker thread completes. */
    g_thread_join(process_g_thread);

    /* Decrement the reference count for the main loop. */
    g_main_loop_unref(gloop);
}

Inside process_thread worker, send calls to a thread with access to the main GTK loop for updating the UI.

gboolean process_thread(gpointer data) {

    while (getline(&csv_line, &len, file_handle) != -1) {

        /* Every time we read a line, increment the current line number. */    
        current_line_number++;

        /* When the main loop is idle, call a function that displays the current line number in the main loop. */
        gdk_threads_add_idle((GSourceFunc)line_number_in_status_bar, data);

    }
}

Inside the thread with access to the main loop, display the current line number.

gboolean line_number_in_status_bar(gpointer data) {

    /* Get the status bar's context, remove any current message. */
    guint status_bar_context_info = gtk_statusbar_get_context_id(GTK_STATUSBAR(status_bar), "STATUS_BAR_CONTEXT_INFO");
    gtk_statusbar_remove(GTK_STATUSBAR(data_passer->status_bar), status_bar_context_info, data_passer->status_bar_context_info_message_id);

    /* Construct a message to display in the status bar. */
    gchar progress_message[100];
    g_snprintf(progress_message, 100, "Reading line %d...", data_passer->current_line_number);

    /* Push the message to the status bar. */
    data_passer->status_bar_context_info_message_id = gtk_statusbar_push(GTK_STATUSBAR(data_passer->status_bar), status_bar_context_info, progress_message);

    /* Return FALSE to remove the source function. */
    return FALSE;
}

Upvotes: 1

Related Questions