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