Inter-Thread and Inter-Process communication
From WxWiki
Contents |
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
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.