I have tried various options, with varying degrees of success. I knew that a good solution would entail multi-threading, but didn't know enough to implement it. Finally I think I have it figured out. Here is a demo program showing the basic technique. It is for the GTKMM C++ interface to Gtk that SimSoup uses.
// GUI_With_WorkThreads.h // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public // License Version 3. // Copyright (C) 2011 Chris Gordon-Smith #ifndef GUI_WITH_WORKTHREADS #define GUI_WITH_WORKTHREADS class GUI_With_WorkThreads : public Gtk::Window { public: GUI_With_WorkThreads(); virtual ~GUI_With_WorkThreads(); private: // Disable copy construction GUI_With_WorkThreads(GUI_With_WorkThreads const & ); GUI_With_WorkThreads const & operator=(GUI_With_WorkThreads const & ); // GUI Wigets Gtk::VBox Main_VBox_; Gtk::Button GUI_Thread_Button_; Gtk::Label GUI_Thread_Button_Press_Counter_Label_; Gtk::Label WorkThread1_Counter_Label_; Gtk::Label WorkThread2_Counter_Label_; Gtk::Button WorkThread1_StartStop_Button_; Gtk::Button WorkThread2_StartStop_Button_; Gtk::Button Exit_Button_; // Thread Progress Counters and Active/Inactive Flags int WorkThread1_Counter_; int WorkThread2_Counter_; int GUI_Thread_Counter_; bool WorkThread1_Active_Fl_; bool WorkThread2_Active_Fl_; // Pointers to the Worker Threads Glib::Thread* WorkThread1_Pt; Glib::Thread* WorkThread2_Pt; // Dispatcher objects for the WorkThread Counters Glib::Dispatcher WorkThread1_Counter_Disp_; Glib::Dispatcher WorkThread2_Counter_Disp_; // Event Handlers running in GUI Thread void On_GUI_Thread_Button_Click(); void On_WorkThread1_Counter_Event(); void On_WorkThread2_Counter_Event(); void On_WorkThread1_StartStop_Click(); void On_WorkThread2_StartStop_Click(); void On_Exit_Click(); // Worker Thread Functions void WorkThread1(); // Runs in Non-GUI thread void WorkThread2(); // Runs in Non-GUI thread }; #endif //GUI_WITH_WORKTHREADS
// GUI_With_WorkThreads.cpp // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // Version 3. // // Copyright (C) 2011 Chris Gordon-Smith //---------------------------------------------------------------------- // This small program demonstrates how a gtkmm based GUI application // can have one or more non-GUI 'worker' threads. This is relevant in // a number of scenarios. A typical example would be a simulation // where the GUI should remain active while the simulation is running. // To compile it you need gtkmm-2.4 installed. // If you are on GNU/Linux, then use pkg-config as follows: // g++ -o GUI_With_WorkThreads GUI_With_WorkThreads.cpp `pkg-config gtkmm-2.4 --libs --cflags` // Key Concepts For Multi-Threading In This Program // ================================================ // The Glib threading system is used. It has to be initialised with a // call to Glib::thread_init(). The GUI code all runs in a 'GUI // Thread'. This is the Glib thread that runs when the program is // started. // Additional non-GUI Glib threads are started with // Glib::Thread::create(). These are used for the Worker Threads. The // Worker Threads are created as 'joinable'. This means that the GUI // thread can invoke the join function to wait for them (which it does // here only to ensure that the application closes cleanly). // A Worker Thread communicates with the GUI Thread using a // Glib::Dispatcher object. This object must have been previously // connected to an event handler that executes in the GUI Thread. This // mechanism is used to enable a Worker Thread to tell the GUI Thread // when thread progress information should be displayed on the // GUI. The Worker Thread invokes its Dispatcher object, and this // causes the connected handler in the GUI Thread to run. // A GUI event handler executes in the Glib Thread that established the // connection, and so the connections in this program are made by code // running in the constructor. #include <gtkmm.h> #include <sstream> #include "GUI_With_WorkThreads.h" GUI_With_WorkThreads::GUI_With_WorkThreads() : Main_VBox_(false,20), GUI_Thread_Button_("GUI Thread"), WorkThread1_Counter_(0), WorkThread2_Counter_(0), GUI_Thread_Counter_(0), WorkThread1_Active_Fl_(true), // Work Thread 1 is active on // application start WorkThread2_Active_Fl_(false), WorkThread1_StartStop_Button_("Start / Stop Thread 1"), WorkThread2_StartStop_Button_("Start / Stop Thread 2"), GUI_Thread_Button_Press_Counter_Label_ ("GUI Thread Button Clicks: 0"), WorkThread1_Counter_Label_("Thread 1 Progress: 0"), WorkThread2_Counter_Label_("Thread 2 Progress: 0"), Exit_Button_("Exit"), WorkThread1_Pt(0), WorkThread2_Pt(0) // Constructor: Executes in the GUI Thread { add(Main_VBox_); // Setup GUI Widgets Main_VBox_.set_border_width(15); Main_VBox_.pack_start(GUI_Thread_Button_Press_Counter_Label_); Main_VBox_.pack_start(GUI_Thread_Button_); Main_VBox_.pack_start(WorkThread1_Counter_Label_); Main_VBox_.pack_start(WorkThread1_StartStop_Button_); Main_VBox_.pack_start(WorkThread2_Counter_Label_); Main_VBox_.pack_start(WorkThread2_StartStop_Button_); Main_VBox_.pack_start(Exit_Button_); // Connect the handler for the GUI Thread Button Click event GUI_Thread_Button_.signal_clicked().connect( sigc::mem_fun(*this, &GUI_With_WorkThreads::On_GUI_Thread_Button_Click)); // Connect the handlers for updates to WorkThread progress // counters. These handlers run in the GUI Thread when the Work // Threads invoke their Glib::Dispatcher objects. WorkThread1_Counter_Disp_.connect( sigc::mem_fun(*this, &GUI_With_WorkThreads::On_WorkThread1_Counter_Event)); WorkThread2_Counter_Disp_.connect( sigc::mem_fun(*this, &GUI_With_WorkThreads::On_WorkThread2_Counter_Event)); // Connect the handlers for the Start/Stop buttons for the Work // Threads WorkThread1_StartStop_Button_.signal_clicked().connect( sigc::mem_fun(*this, &GUI_With_WorkThreads::On_WorkThread1_StartStop_Click)); WorkThread2_StartStop_Button_.signal_clicked().connect( sigc::mem_fun(*this, &GUI_With_WorkThreads::On_WorkThread2_StartStop_Click)); // Create joinable thread for the Work Threads that are acive on // application startup if(WorkThread1_Active_Fl_) WorkThread1_Pt = Glib::Thread::create( sigc::mem_fun(*this,&GUI_With_WorkThreads::WorkThread1),true); if(WorkThread2_Active_Fl_) WorkThread1_Pt = Glib::Thread::create( sigc::mem_fun(*this,&GUI_With_WorkThreads::WorkThread2),true); // Connect the handler for program exit Exit_Button_.signal_clicked().connect( sigc::mem_fun(*this, &GUI_With_WorkThreads::On_Exit_Click)); show_all(); } //----------------------------------------------------------------- void GUI_With_WorkThreads::On_WorkThread1_Counter_Event() // Update the WorkThread 1 Progress Counter on the GUI. This function // executes in the GUI Thread { std::ostringstream oss; oss << "Thread 1 Progress: " << WorkThread1_Counter_++; WorkThread1_Counter_Label_.set_text(oss.str()); } //--------------------------------- void GUI_With_WorkThreads::On_WorkThread2_Counter_Event() // Update the WorkThread 2 Progress Counter on the GUI. This fuction // executes in the GUI Thread { std::ostringstream oss; oss << "Thread 2 Progress: " << WorkThread2_Counter_++; WorkThread2_Counter_Label_.set_text(oss.str()); } //---------------------------------------------------------------------- void GUI_With_WorkThreads::On_WorkThread1_StartStop_Click() // Start / Stop Work Thread 1. If starting, create a new Glib::Thread // This function executes in the GUI Thread { WorkThread1_Active_Fl_ = !WorkThread1_Active_Fl_; // Create a joinable Glib::Thread for Work Thread 1 WorkThread1_Pt = Glib::Thread::create( sigc::mem_fun(*this,&GUI_With_WorkThreads::WorkThread1),true); } //---------------------------------------------------------------------- void GUI_With_WorkThreads::On_WorkThread2_StartStop_Click() // Start / Stop Work Thread 2. If starting, create a new Glib::Thread // This function executes in the GUI Thread { WorkThread2_Active_Fl_ = !WorkThread2_Active_Fl_; // Create a joinable Glib thread for Work Thread 2 WorkThread2_Pt = Glib::Thread::create( sigc::mem_fun(*this,&GUI_With_WorkThreads::WorkThread2),true); } //---------------------------------------------------------------------- GUI_With_WorkThreads::~GUI_With_WorkThreads() // If the program window is exited using the window manager to close // its window, then exit cleanly anyway { On_Exit_Click(); } // -------------------------------------------------------------------- void GUI_With_WorkThreads::On_GUI_Thread_Button_Click() // Update and display the GUI Thread progress counter // This function executes in the GUI Thread { std::ostringstream oss; oss << "GUI Thread Button Clicks: " << ++GUI_Thread_Counter_; GUI_Thread_Button_Press_Counter_Label_.set_text(oss.str()); } //---------------------------------------------------------------------- void GUI_With_WorkThreads::WorkThread1() // This is the main loop for Work Thread 1. It runs in a Non GUI // Thread. So long as Work Thread 1 is active, it loops and keeps the // GUI Thread updated on progress. { while(WorkThread1_Active_Fl_) { for(int Iloop = 0; Iloop < 1000000; Iloop++) { // We could do some work here! } // Invoke the GUI Thread to display the progress counter for // this Work Thread WorkThread1_Counter_Disp_(); } } //---------------------------------------------------------------------- void GUI_With_WorkThreads::WorkThread2() // This is the main loop for Work Thread 2. It runs in a Non GUI // Thread. So long as Work Thread 2 is active, it loops and keeps the // GUI Thread updated on progress. { while(WorkThread2_Active_Fl_) { for(int Iloop = 0; Iloop< 1000000; Iloop++) { // We could do some work here! } // Invoke the GUI Thread to display the progress counter for // this Work Thread WorkThread2_Counter_Disp_(); } } //---------------------------------------------------------------------- void GUI_With_WorkThreads::On_Exit_Click() // If the user presses exit, inactivate the Worker Threads, wait for // them to complete, and exit the program. { WorkThread1_Active_Fl_ = false; WorkThread2_Active_Fl_ = false; if (WorkThread1_Pt) WorkThread1_Pt->join(); if (WorkThread2_Pt) WorkThread2_Pt->join(); exit(0); } //---------------------------------------------------------------------- int main(int argc, char**argv) { Glib::thread_init(); // Initialise the multithreading system Gtk::Main Application(argc,argv); GUI_With_WorkThreads Application_Window; Application.run(Application_Window); return EXIT_SUCCESS; }