Inter-Thread and Inter-Process communication

From WxWiki
Jump to: navigation, search

Inter-Thread Communication

You are best advised to handle inter-thread communication by means of the wxWidgets event handling system, more precisely, by posting events to the message handler of the parent.

Usually, there is the classical main-thread-worker-thread scenario.

Sending events to the main thread - wxWidgets 3 only

For wxWidgets 3, it is possible, in addition to the below methods to make use of CallAfter as a potentially 'cleaner' way of doing this will less boiler plate code. e.g.

  • thread.cpp
#include "app.h"

wxThread::ExitCode MyThread::Entry()
{
    for (int n=0; n<5000; n++)
    {
        this->Sleep(500);
        
        //Pass some data to a function in main UI thread
        wxTheApp->GetTopWindow()->GetEventHandler()->CallAfter(boost::bind(&SomeClass::SomeFunction,PointerToClassInstance,n));
    }
    return 0;
}

Sending events to the main thread

For simple cases, there is no need to create a custom event, you can simply use an existing event type with a specific ID.

  • thread.h
#include "wx/wx.h"

// the ID we'll use to identify our event
const int NUMBER_UPDATE_ID = 100000;

// a thread class that will periodically send events to the GUI thread
class MyThread : public wxThread
{
    wxFrame* m_parent;
public:
    MyThread(wxFrame* parent)
    {
        m_parent = parent;
    }
    
    virtual ExitCode Entry();
};
  • thread.cpp
#include "thread.h"

wxThread::ExitCode MyThread::Entry()
{
    for (int n=0; n<5000; n++)
    {
        this->Sleep(500);
        
        // notify the main thread
        wxCommandEvent event( wxEVT_COMMAND_TEXT_UPDATED, NUMBER_UPDATE_ID );
        event.SetInt(n);  // pass some data along the event, a number in this case
        m_parent->GetEventHandler()->AddPendingEvent( event );
    }
    return 0;
}
  • main.cpp
#include "wx/wx.h"
#include "wx/spinctrl.h"

#include "thread.h"

class MyFrame : public wxFrame
{
    wxSpinCtrl* m_spinner;
    
public:
    
    MyFrame() : wxFrame(NULL, wxID_ANY,  wxT("Hello wxWidgets"), wxPoint(50,50), wxSize(800,600))
    {
        wxPanel* panel = new wxPanel(this);
        
        wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
        m_spinner = new wxSpinCtrl(panel);
        
        sizer->Add(m_spinner);
        panel->SetSizer(sizer);
    }
    
    // this is called when the event from the thread is received
    void onNumberUpdate(wxCommandEvent& evt)
    {
        // get the number sent along the event and use it to update the GUI
        m_spinner->SetValue( evt.GetInt() );
    }
    
    DECLARE_EVENT_TABLE()
};

// catch the event from the thread
BEGIN_EVENT_TABLE(MyFrame, wxFrame)
EVT_COMMAND  (NUMBER_UPDATE_ID, wxEVT_COMMAND_TEXT_UPDATED, MyFrame::onNumberUpdate)
END_EVENT_TABLE()


class MyApp: public wxApp
{
    wxFrame* m_frame;
public:
    
    bool OnInit()
    {
        m_frame = new MyFrame();
        m_frame->Show();
        
        // create the thread
        MyThread* t = new MyThread(m_frame);
        wxThreadError err = t->Create();
        
        if (err != wxTHREAD_NO_ERROR)
        {
            wxMessageBox( _("Couldn't create thread!") );
            return false;
        }
        
        err = t->Run();
        
        if (err != wxTHREAD_NO_ERROR)
        {
            wxMessageBox( _("Couldn't run thread!") );
            return false;
        }
        
        return true;
    } 
    
};

IMPLEMENT_APP(MyApp)

Sending custom events to the main thread

The example below fully illustrates how to create a new event type; but keep in mind that for simple needs, it might be enough to use a wxCommandEvent with a specific ID reserved for this task (see above)

Often it will be necessary for a thread to send information to the main thread - this is done through custom events.

The example below will use an event to inform the main thread that the worker thread has to prematurely terminate because of an internal error. Those state changes are handled by the main thread's event handler(s) that decide what to do. This will be done through a custom event - you can use custom events for other uses as well.

In the example below, a worker thread will notify its parent (the main thread, MainDialog) of an internal error and terminate. The main thread will then invalidate the thread pointer since detached threads delete themselves automatically (see wxThread deletion in the wxW docs). In MainDialog's destructor, the wxThread::Delete() member function is called to gracefully terminate the detached thread.


  • In the main thread code :
  class MainDialog : public wxDialog
  {
  private:
    DECLARE_EVENT_TABLE()
    WorkerThread* m_pThread;
  public:
    MainDialog() : wxDialog(), m_pThread(NULL)
    {
      m_pThread=new WorkerThread(this);
      if(wxTHREAD_NO_ERROR!=m_pThread->Create()) { /* handle creation error here! */ }
      m_pThread->Run(); // run the thread
    }
    ~MainDialog() { m_pThread->Delete(); m_pThread=NULL; } // gracefully terminate worker thread

    // the callback for our custom event
    void OnThread (wxCommandEvent& evt)
    {
      // handle thread events here, i. e. a premature end due to internal error
      m_pThread=NULL; // thread terminated itself (detached, m_pThread was freed automatically)
    }
  };

  // how to catch a custom event : like any regular event
  BEGIN_EVENT_TABLE(MainDialog, wxDialog)
  EVT_COMMAND(wxID_ANY, wxEVT_MY_EVENT, MainDialog::OnThread)
  END_EVENT_TABLE()
  • In the worker thread code :
  // how to declare a custom event. this can go in a header
  DECLARE_EVENT_TYPE(wxEVT_MY_EVENT, -1)

  // how to define the custom event
  DEFINE_EVENT_TYPE(wxEVT_MY_EVENT)

  class WorkerThread:public wxThread
  {
  protected:
    wxEvtHandler* m_pParent;
  public:
    WorkerThread(wxEvtHandler* pParent) : wxThread(), m_pParent(pParent) {}

    wxThread::ExitCode Entry()
    {
      int iError=EXIT_SUCCESS;
      while(!TestDestroy())
      {
        // do some work
        if(iError!=EXIT_SUCCESS) break; // some error has caused iError to be !=EXIT_SUCCESS
      }
      if(iError==EXIT_SUCCESS) return iError; // no need to notify parent, termination was intended
      
      // if we get here, the thread ended because of an internal error.
      // we'll use a custom event to notify parent

      // how to throw a custom event
      wxCommandEvent evt(wxEVT_MY_EVENT, wxID_ANY);
      evt.SetInt(iError); // we can pass data through the event
      m_pParent->AddPendingEvent(evt);

      return iError;
    }
  };

Note that wxThread::Delete() is a blocking function which waits until the worker thread has exited wxThread::Entry(). Only call wxThread::Delete() on a detached thread if you are absolutely sure that it is still running. We can do this in the MainDialog destructor since we trust that WorkerThread sends an event whenever it decides to terminate itself.

wxWidgets 2.8 only (in 2.9 wxString is not reference-counted by default): if you use custom events containing wxString, you must ensure that strings are deeply copied, e.g. by using .c_str(), as they are reference-counted and hence shared by default, which results in problems if the same string object is accessed from more than one thread.

Bidirectional Communication

If you need bidirectional communication, i.e. the main thread to tell the worker thread what it has to do and the worker thread answering to those requests, it might be tempting to implement the class WorkerThread using multiple inheritance, making it child of wxEvtHandler (to receive notifications and handle events) and wxThread (order of inheritance matters - at least with MSVC2005 to avoid typecasting problems) and implement an event table for wxEVT_THREAD.

That, however, doesn't solve the problem, because the worker thread's event handler doesn't run its own event loop and so all events are run in the main thread's context which is clearly not what we want.

There's a thread in the newsgroup comp.soft-sys.wxwindows that covers this issue. The moral is:

[...] In wxWindows all event handling occurs always in main thread.

[Aj Lavin uses] a similar trick in my ThreadHandler code to process events in a thread: 

http://www.ajlavin.com/doc/ppthread/index.html
The basic idea is to override wxEvtHandler::ProcessEvent in the 
thread-based event handler class. The implementation pushes the event 
onto a threadsafe message queue. The thread main loop can be written to 
consume events as they become available in the queue, calling 
wxEvtHandler::ProcessEvent on each one. 

So an event that gets posted to the thread-based event handler class, 
either through wxPostEvent or the ProcessEvent event method, gets added 
to your message queue by the main thread, then dispatched to the correct 
event handler in the handler's thread.

As a consequence, a long operation that is supposed to run in the worker thread and has been triggererd by an event handled by the standard implementation of wxEvtHandler, is actually not running in the worker thread's context but blocking the main thread instead.

Read Multithreading Overview in the wxWidgets documentation for more explanations about the background.

New wxMessageQueue Class

In the latest wx sources there is a template wxMessageQueue class which provides a thread-safe (and type-safe) message queue which can be used to pass messages to a worker thread. As this class is entirely implemented in the single include/wx/msgqueue.h header file, it can be easily used by copying the latest version to your own project include directory.

To use it, simply call Post() to pass a message and Receive() or ReceiveTimeout() in the worker thread.

Job/Queue Manager

As a somewhat simpler remedy--and when you just need one single worker thread that waits for jobs--in this guide we're using events when the worker thread wants to communicate with the main thread and a specially-designed FIFO-buffer (the job manager) when the main thread wants to add jobs for the worker thread.

  // events for worker thread
  DECLARE_EVENT_TYPE(wxEVT_THREAD, -1)
  DEFINE_EVENT_TYPE(wxEVT_THREAD)

  class tJOB
  {
  public:
    enum tCOMMANDS // list of commands that are currently implemented
    {
      eID_THREAD_EXIT=wxID_EXIT, // thread should exit or wants to exit
      eID_THREAD_NULL=wxID_HIGHEST+1, // dummy command
      eID_THREAD_STARTED, // worker thread has started OK
    }; // enum tCOMMANDS
  
    tJOB() : m_cmd(eID_THREAD_NULL) {}
    tJOB(tCOMMANDS cmd, const wxString& arg) : m_cmd(cmd), m_Arg(arg) {}
    tCOMMANDS m_cmd; wxString m_Arg;
  }; // class tJOB
  
  class QUEUE
  {
  public:
    enum tPRIORITY { eHIGHEST, eHIGHER, eNORMAL, eBELOW_NORMAL, eLOW }; // priority classes
    QUEUE(wxEvtHandler* pParent) : m_pParent(pParent) {}
    void AddJob(const tJOB& job, const tPRIORITY& priority=eNORMAL) // push a job with given priority class onto the FIFO
    {
      wxMutexLocker lock(m_MutexQueue); // lock the queue
      m_Jobs.insert(std::make_pair(priority, job)); // insert the prioritized entry into the multimap
      m_QueueCount.Post(); // new job has arrived: increment semaphore counter
    } // void AddJob(const tJOB& job, const tPRIORITY& priority=eNORMAL)
    tJOB Pop()
    {
      tJOB element;
      m_QueueCount.Wait(); // wait for semaphore (=queue count to become positive)
      m_MutexQueue.Lock(); // lock queue
      element=(m_Jobs.begin())->second; // get the first entry from queue (higher priority classes come first)
      m_Jobs.erase(m_Jobs.begin()); // erase it
      m_MutexQueue.Unlock();// unlock queue
      return element; // return job entry
    } // tJOB Pop()
    void Report(const tJOB::tCOMMANDS& cmd, const wxString& arg=wxEmptyString) // report back to parent
    {
      wxCommandEvent evt(wxEVT_THREAD, cmd); // create command event object
      evt.SetString(arg); // associate string with it
      m_pParent->AddPendingEvent(evt); // and add it to parent's event queue
    } // void Report(const tJOB::tCOMMANDS& cmd, const wxString& arg=wxEmptyString)
    size_t Stacksize() // helper function to return no of pending jobs
    {
      wxMutexLocker lock(m_MutexQueue); // lock queue until the size has been read
      return m_Jobs.size();
    }
  
  private:
    wxEvtHandler* m_pParent;
    std::multimap<tPRIORITY, tJOB> m_Jobs; // multimap to reflect prioritization: values with lower keys come first, newer values with same key are appended
    wxMutex m_MutexQueue; // protects queue access
    wxSemaphore m_QueueCount; // semaphore count reflects number of queued jobs
  };

QUEUE will accept jobs in QUEUE::AddJob() by appending a tJOB "job" to a std::multimap and get the next job via QUEUE::Pop(). Both functions are using wxSemaphore with an unlimited maxcount value to block QUEUE::Pop() when the queue is empty (=the semaphore count has reached zero) until a new job has arrived. The choice of std::multimap allows for job prioritization because jobs (=keys) with higher priorities (=lower key values) are automatically inserted before those with lower priorities and new jobs with identical priorities are just appended to existing ones. Removing jobs from the beginning of the map will therefore always get the oldest job with the highest priority first.

This setup will allow a quite simple main loop in the worker thread:

    virtual wxThread::ExitCode Entry()
    {
      tJOB::tCOMMANDS iErr;
      m_pQueue->Report(tJOB::eID_THREAD_STARTED); // tell main thread that worker thread has successfully started
      try { while(true) OnJob(); } // this is the main loop: process jobs until a job handler throws
      catch(tJOB::tCOMMANDS& i) { m_pQueue->Report(iErr=i); } // catch return value from error condition
      return (wxThread::ExitCode)iErr; // and return exit code
    } // virtual wxThread::ExitCode Entry()

Note that the main thread is notified of thread termination and internal errors (tJOB::eID_THREAD_EXIT) by adding a pending event to the parent's message queue. This is done via QUEUE::Report() and the custom event type wxEVT_THREAD.

Processing jobs is also quite straightforward:

    virtual void OnJob()
    {
      tJOB job=m_pQueue->Pop(); // pop a job from the queue. this will block the worker thread if queue is empty
      switch(job.m_cmd)
      {
      case tJOB::eID_THREAD_EXIT: Sleep(1000); throw tJOB::eID_THREAD_EXIT; // worker thread should exit: wait a while, then confirm the command
      case tJOB::eID_THREAD_NULL: // dummy command
      default: break;
      } // switch(job.m_cmd)
    } // virtual void OnJob()
  }; // class WorkerThread : public wxThread

Starting, Stopping, Synchronization

Starting the worker thread involves setting up the queue, creating the worker thread instance and finally running it:

  m_pQueue=new QUEUE(this);
  WorkerThread* pThread=new WorkerThread(m_pQueue); // create a new worker thread
  pThread->Run();

Note that it's not necessary (not even advised!) to put the instance of WorkerThread in a member variable. All communication is done through m_pQueue and since the worker thread is detached, it will delete itself (freeing instance memory) after returning from wxThread::Entry().

In wxThread::ExitCode Entry(), the workerthread will automatically inform the main thread about successful startup by sending a tJOB::eID_THREAD_STARTED message to it.

Shutting down a thread (i. e. when the main thread/wxWindow has to be closed) is done in several steps:

BEGIN_EVENT_TABLE(MyFrame, wxFrame)
  EVT_COMMAND(wxID_ANY, wxEVT_THREAD, MyFrame::OnThread)
  EVT_MENU(eQUIT, MyFrame::OnQuit)
  EVT_CLOSE(MyFrame::OnClose)
END_EVENT_TABLE()
 
void OnStop(wxCommandEvent& WXUNUSED(event)) // stop worker thread
  {
    if(!m_bThreadStarted) Destroy(); // thread hasn't been started yet: frame can be destroyed right away
    m_pQueue->AddJob(tJOB(tJOB::eID_THREAD_EXIT, wxEmptyString), QUEUE::eHIGHEST); // add eID_THREAD_EXIT notification with highest priority to bypass other running jobs
  }
void OnThread(wxCommandEvent& event) // handler for thread notifications
  {
    switch(event.GetId())
    {
    case tJOB::eID_THREAD_EXIT: m_bThreadStarted=false; Destroy(); break; // thread wants to exit: disable controls and destroy main window
    case tJOB::eID_THREAD_STARTED: m_bThreadStarted=true; break; // thread has successfully started
    default: event.Skip();
    }
}
void OnQuit(wxCommandEvent& WXUNUSED(event)) { Close(); } // essentially the same as OnClose()
void OnClose(wxCloseEvent& WXUNUSED(event))  { wxCommandEvent e; OnStop(e); } // just run OnStop() which will terminate the worker thread and destroy the main frame

1. The wxID_EXIT/quit/close event is handled. This will result in a job eID_THREAD_EXIT being added with highest priority to make it run immediately after the currently running job has finished or wake up the worker thread if it's just waiting for the queue to be filled.

2. The worker thread handles the eID_THREAD_EXIT job which will make it throw tJOB::eID_THREAD_EXIT, effectively leaving the main loop through the catch{} handler.

3. A pending event of type eID_THREAD_EXIT will be added to the main thread's event queue, informing it that the thread has ended and that it is now safe to call wxWindow::Destroy() to destroy the main window and leave the app.

2b. If the worker thread experiences an unrecoverable internal error, it will throw tJOB::eID_THREAD_EXIT) which will send an event with ID eID_THREAD_EXIT to the main thread, notifying it that it had to exit. The main thread will exit as described in 3.

Thread Pooling

The example above can be very easily expanded to a set of threads. Since the QUEUE class will protect its FIFO buffer from concurrent access, it will deliver a new job (which is due for schedule because of its age and priority) to every thread that calls QUEUE::Pop().

Multiple threads can be started via multiply running

WorkerThread* pThread=new WorkerThread(m_pQueue); // create a new worker thread
pThread->Run();

as described in the Synchronization chapter.

You should be aware of the number of threads when shutting down (you have to emit tJOB::eID_THREAD_EXIT n-times from the main thread and also wait for n confirmations) by either just counting or assigning them an unique ID in the constructor and make them report it back through QUEUE::Report() so you are aware of what/how many thread(s) is/are still running and which one has successfully terminated.

Complete Example

The following example shows a wxFrame that runs a thread pool to which you can add/remove worker threads and assign jobs to them. The application will terminate if there are no more worker threads left and there's also an option to add an "errorneous" job to show communication in the inverse direction: The worker thread sending a notification to the main thread about an internal error that made it exit.

  // wx stuff
  #include <wx/frame.h>
  #include <wx/thread.h>
  #include <wx/menu.h>
  #include <wx/app.h>
  
  // misc stuff
  #include <stdlib.h>
  #include <assert.h>
  #include <map>
  #include <list>
  
  // events for worker thread
  DECLARE_EVENT_TYPE(wxEVT_THREAD, -1)
  DEFINE_EVENT_TYPE(wxEVT_THREAD)
  
  class tJOB
  {
  public:
    enum tCOMMANDS // list of commands that are currently implemented
    {
      eID_THREAD_EXIT=wxID_EXIT, // thread should exit or wants to exit
      eID_THREAD_NULL=wxID_HIGHEST+1, // dummy command
      eID_THREAD_STARTED, // worker thread has started OK
      eID_THREAD_JOB, // process normal job
      eID_THREAD_JOBERR // process errorneous job after which thread likes to exit
    }; // enum tCOMMANDS
  
    tJOB() : m_cmd(eID_THREAD_NULL) {}
    tJOB(tCOMMANDS cmd, const wxString& arg) : m_cmd(cmd), m_Arg(arg) {}
    tCOMMANDS m_cmd; wxString m_Arg;
  }; // class tJOB
  
  class QUEUE
  {
  public:
    enum tPRIORITY { eHIGHEST, eHIGHER, eNORMAL, eBELOW_NORMAL, eLOW, eIDLE }; // priority classes
    QUEUE(wxEvtHandler* pParent) : m_pParent(pParent) {}
    void AddJob(const tJOB& job, const tPRIORITY& priority=eNORMAL) // push a job with given priority class onto the FIFO
    {
      wxMutexLocker lock(m_MutexQueue); // lock the queue
      m_Jobs.insert(std::make_pair(priority, job)); // insert the prioritized entry into the multimap
      m_QueueCount.Post(); // new job has arrived: increment semaphore counter
    } // void AddJob(const tJOB& job, const tPRIORITY& priority=eNORMAL)
    tJOB Pop()
    {
      tJOB element;
      m_QueueCount.Wait(); // wait for semaphore (=queue count to become positive)
      m_MutexQueue.Lock(); // lock queue
      element=(m_Jobs.begin())->second; // get the first entry from queue (higher priority classes come first)
      m_Jobs.erase(m_Jobs.begin()); // erase it
      m_MutexQueue.Unlock(); // unlock queue
      return element; // return job entry
    } // tJOB Pop()
    void Report(const tJOB::tCOMMANDS& cmd, const wxString& sArg=wxEmptyString, int iArg=0) // report back to parent
    {
      wxCommandEvent evt(wxEVT_THREAD, cmd); // create command event object
      evt.SetString(sArg); // associate string with it
      evt.SetInt(iArg);
      m_pParent->AddPendingEvent(evt); // and add it to parent's event queue
    } // void Report(const tJOB::tCOMMANDS& cmd, const wxString& arg=wxEmptyString)
    size_t Stacksize() // helper function to return no of pending jobs
    {
      wxMutexLocker lock(m_MutexQueue); // lock queue until the size has been read
      return m_Jobs.size();
    }
  
  private:
    wxEvtHandler* m_pParent;
    std::multimap<tPRIORITY, tJOB> m_Jobs; // multimap to reflect prioritization: values with lower keys come first, newer values with same key are appended
    wxMutex m_MutexQueue; // protects queue access
    wxSemaphore m_QueueCount; // semaphore count reflects number of queued jobs
  };
  
  class WorkerThread : public wxThread
  {
  public:
    WorkerThread(QUEUE* pQueue, int id=0) : m_pQueue(pQueue), m_ID(id) { assert(pQueue); wxThread::Create(); }
  
  private:
    QUEUE* m_pQueue;
    int m_ID;
  
    virtual wxThread::ExitCode Entry()
    {
      Sleep(1000); // sleep a while to simulate some time-consuming init procedure
      tJOB::tCOMMANDS iErr;
      m_pQueue->Report(tJOB::eID_THREAD_STARTED, wxEmptyString, m_ID); // tell main thread that worker thread has successfully started
      try { while(true) OnJob(); } // this is the main loop: process jobs until a job handler throws
      catch(tJOB::tCOMMANDS& i) { m_pQueue->Report(iErr=i, wxEmptyString, m_ID); } // catch return value from error condition
      return (wxThread::ExitCode)iErr; // and return exit code
    } // virtual wxThread::ExitCode Entry()
  
    virtual void OnJob()
    {
      tJOB job=m_pQueue->Pop(); // pop a job from the queue. this will block the worker thread if queue is empty
      switch(job.m_cmd)
      {
      case tJOB::eID_THREAD_EXIT: // thread should exit
        Sleep(1000); // wait a while
        throw tJOB::eID_THREAD_EXIT; // confirm exit command
      case tJOB::eID_THREAD_JOB: // process a standard job
        Sleep(2000);
        m_pQueue->Report(tJOB::eID_THREAD_JOB, wxString::Format(wxT("Job #%s done."), job.m_Arg.c_str()), m_ID); // report successful completion
        break;
      case tJOB::eID_THREAD_JOBERR: // process a job that terminates with an error
        m_pQueue->Report(tJOB::eID_THREAD_JOB, wxString::Format(wxT("Job #%s errorneous."), job.m_Arg.c_str()), m_ID);
        Sleep(1000);
        throw tJOB::eID_THREAD_EXIT; // report exit of worker thread
        break;
      case tJOB::eID_THREAD_NULL: // dummy command
      default: break; // default
      } // switch(job.m_cmd)
    } // virtual void OnJob()
  }; // class WorkerThread : public wxThread
  
  
  // ----------------------------------------------------------------------------
  // main frame
  // ----------------------------------------------------------------------------
  class MyFrame : public wxFrame
  {
    enum { eQUIT=wxID_CLOSE, eSTART_THREAD=wxID_HIGHEST+100 };
    DECLARE_DYNAMIC_CLASS(MyFrame)
  public:
    MyFrame(const wxString& title) : wxFrame(NULL, wxID_ANY, title), m_pQueue(NULL)
    {
      wxMenu *fileMenu=new wxMenu;
      fileMenu->Append(eSTART_THREAD,   _T("&Start a thread\tAlt-S"),     _T("Starts one worker thread"));
      fileMenu->Append(tJOB::eID_THREAD_EXIT,     _T("Sto&p a thread\tAlt-P"), _T("Stops one worker thread"));
      fileMenu->Append(tJOB::eID_THREAD_JOB,      _T("&Add job to thread\tAlt-A"),  _T("Adds a job to the worker thread"));
      fileMenu->Append(tJOB::eID_THREAD_JOBERR,   _T("Add &errorneous job to thread\tAlt-E"), _T("Adds am errorneous job to the worker thread"));
      fileMenu->Append(eQUIT,      _T("E&xit\tAlt-X"),  _T("Exit this program"));
      wxMenuBar *menuBar=new wxMenuBar();
      menuBar->Append(fileMenu, _T("&Options"));
      SetMenuBar(menuBar);
      EnableThreadControls(false); // disable thread controls since worker thread isn't running yet
      fileMenu->Enable(eSTART_THREAD, true); // starting threads should always be possible
      CreateStatusBar();
      SetStatusText(_T("Worker thread sample"));
      m_pQueue=new QUEUE(this);
    } // MyFrame(const wxString& title)
    ~MyFrame() { delete m_pQueue; }
    void EnableThreadControls(bool bEnable)
    {
      wxMenu* pMenu=GetMenuBar()->GetMenu(0);
      static const int MENUIDS[]={eSTART_THREAD, tJOB::eID_THREAD_EXIT, tJOB::eID_THREAD_JOB, tJOB::eID_THREAD_JOBERR};
      for(int i=0; i<WXSIZEOF(MENUIDS); pMenu->Enable(MENUIDS[i++], bEnable));
    }
    void OnStart(wxCommandEvent& WXUNUSED(event)) // start one worker thread
    {
      int id=m_Threads.empty()?1:m_Threads.back()+1;
      m_Threads.push_back(id);
      WorkerThread* pThread=new WorkerThread(m_pQueue, id); // create a new worker thread, increment thread counter (this implies, thread will start OK)
      pThread->Run();
      SetStatusText(wxString::Format(wxT("[%i]: Thread started."), id));
    }
    void OnStop(wxCommandEvent& WXUNUSED(event)) // stop one worker thread
    {
      if(m_Threads.empty()) { EnableThreadControls(false); Destroy(); return; } // no thread(s) running: frame can be destroyed right away
      m_pQueue->AddJob(tJOB(tJOB::eID_THREAD_EXIT, wxEmptyString), QUEUE::eHIGHEST); // add eID_THREAD_EXIT notification with highest priority to bypass other running jobs
      SetStatusText(_T("Stopping thread..."));
    }
    void OnJob(wxCommandEvent& event) // handler for launching a job for worker thread(s)
    {
      int iJob=rand();
      m_pQueue->AddJob(tJOB((tJOB::tCOMMANDS)event.GetId(), wxString::Format(wxT("%u"), iJob)));
      SetStatusText(wxString::Format(wxT("Job #%i started."), iJob)); // just set the status text
    }
    void OnThread(wxCommandEvent& event) // handler for thread notifications
    {
      switch(event.GetId())
      {
      case tJOB::eID_THREAD_JOB:
        SetStatusText(wxString::Format(wxT("[%i]: %s"), event.GetInt(), event.GetString().c_str())); // progress display
        break; 
      case tJOB::eID_THREAD_EXIT:
        SetStatusText(wxString::Format(wxT("[%i]: Stopped."), event.GetInt()));
        m_Threads.remove(event.GetInt()); // thread has exited: remove thread ID from list
        if(m_Threads.empty()) { EnableThreadControls(false); Destroy(); } // destroy main window if no more threads
        break;
      case tJOB::eID_THREAD_STARTED:
        SetStatusText(wxString::Format(wxT("[%i]: Ready."), event.GetInt()));
        EnableThreadControls(true); // at least one thread successfully started: enable controls
        break; 
      default: event.Skip();
      }
    }
    void OnQuit(wxCommandEvent& WXUNUSED(event))
    {
      if(m_Threads.empty()) { Destroy(); return; } // no thread(s) running - exit right away
      for(size_t t=0; t<m_Threads.size(); ++t) m_pQueue->AddJob(tJOB(tJOB::eID_THREAD_EXIT, wxEmptyString), QUEUE::eHIGHEST); } // send all running threads the "EXIT" signal
    void OnClose(wxCloseEvent& WXUNUSED(event))  { wxCommandEvent e; OnQuit(e); } // just run OnQuit() which will terminate worker threads and destroy the main frame
  private:
    MyFrame() : wxFrame() {}
    QUEUE* m_pQueue;
    std::list<int> m_Threads;
    DECLARE_EVENT_TABLE()
  }; // class MyFrame : public wxFrame
  IMPLEMENT_DYNAMIC_CLASS(MyFrame, wxFrame)
  BEGIN_EVENT_TABLE(MyFrame, wxFrame)
  EVT_MENU(tJOB::eID_THREAD_JOB, MyFrame::OnJob)
  EVT_MENU(tJOB::eID_THREAD_JOBERR, MyFrame::OnJob)
  EVT_MENU(eSTART_THREAD, MyFrame::OnStart)
  EVT_MENU(tJOB::eID_THREAD_EXIT, MyFrame::OnStop)
  EVT_COMMAND(wxID_ANY, wxEVT_THREAD, MyFrame::OnThread)
  EVT_MENU(eQUIT, MyFrame::OnQuit)
  EVT_CLOSE(MyFrame::OnClose)
  END_EVENT_TABLE()
  
  // ----------------------------------------------------------------------------
  // the application
  // ----------------------------------------------------------------------------
  class MyApp : public wxApp
  {
  public:
    virtual bool OnInit()
    {
      if(!wxApp::OnInit()) return false;
      MyFrame *frame=new MyFrame(_T("Minimal wxWidgets App"));
      frame->Show(true);
      return true;
    }
  }; // class MyApp : public wxApp
  IMPLEMENT_APP(MyApp)

Inter-Process Communication

Basics

Interprocess communication is done by means of the wxWidgets classes wxClient and wxServer, both using wxConnection. Depending on the platform, this might result in a socket that is established or MS DDE (in case wxUSE_DDE_FOR_IPC has been defined) being used.

Usually, you would like to derive your own classes from wxServer (i.e., intercept the wxServer::OnAcceptConnection callback function and be notified when an incoming connection has been established), wxConnection (for the server side handlers OnPoke(), OnStartAdvise(), OnStopAdvise(), Advise(), OnDisconnect() and the client side handlers Poke(), OnAdvise() and OnDisconnect()) and wxClient (usually OnMakeConnection() that will return a new wxConnection object).

Server

Enabling the server works in several steps:

  • Create a new instance of the derived wxServer class.
  • Call wxMyServer::Create() with the server's name on it.
  • Handle wxMyServer::OnAcceptConnection() by returning a new wxConnection object.
  • Implement a new function wxMyServer::Advise() which calls wxConnection::Advise() so the server can send the client some data (after wxConnection::OnStartAdvise() has been called, showing that the client requested the advise functionality from the server).
  • Handle wxConnection::OnDisconnect() by calling wxConnection::Disconnect() and passing on this state to the wxMyServer object who will then delete the wxConnection object.

Client

Connecting the client with a server works in the following steps:

  • Create a new (derived) wxClient class instance.
  • Call wxMyClient::Connect() on this newly created instance, specifying host name, service string and topic.
  • In the wxMyClient::Connect() implementation, call the wxClient::MakeConnection base function with the passed in data about host name etc. and immediately call wxConnection::StartAdvise() with a suitable topic if the client should request advice messages from the server. wxConnection::StartAdvise() will trigger a wxConnection::OnStartAdvise() on the server side and the value returned here (true for request granted) will be passed on to the StartAdvise() function.
  • Implement wxMyClient::OnMakeConnection() to return a new instance of the newly created wxConnection object.
  • Handle the wxMyConnection::OnPoke() implementation to call the wxConnection::Poke() base function with the data that is to be sent to the server.
  • Handle wxMyConnection::OnDisconnect() so that the wxMyClient instance is deleted. In wxMyClient's destructor, make sure to call wxConnection::StopAdvise() if a previous StartAdvise() was called, then call wxConnection::Disconnect() and later delete the wxConnection instance.
  • Handle wxConnection::OnDisconnect() to catch server sided disconnections of the communication link by following the steps mentioned above.

Data Transfer

To transfer data from the server to the client (and assuming the server has allowed advice by returning true in wxMyConnection::OnStartAdvise()), call wxMyConnection::Advise() with a suitable item, data, data size and IPC format. For the opposite direction call wxMyConnection::Poke() with similar parameters to poke data into the server.

Other Resources

Check out wxWidgets IPC Overview on the web for more information.