Thursday, January 30, 2020

Multithreading with wxWidgets

In this article, the use of multithreading with wxWidgets is discussed using C++ example programs. The following usage examples are discussed and detailed explanations for each of the step and function of using multhreading are presented.


A basic multithreading example, and the result of running the program is shown in the following link and the figure.

https://github.com/yan9a/cewx/blob/master/thread/thread1/thread1.cpp




Figure 1. thread1.cpp


Simple Multithreading Example

As a simple multithreading example, the program at the following link is discussed.

https://github.com/yan9a/cewx/blob/master/thread/th-simple/th-simple.cpp

Firstly, start, stop, pause, and resume buttons are added. Start button is used to create and start a new thread. Clicking the 'Stop' button looks for the last created thread and delete it. Pause button is used to pause the last started thread. Resume button is used to look for the last paused thread and run it.

Changing Log Target

A list box called 'logList' is created to show messages on the user interface. To log messages with timestamp and to limit the number of lines, a function called WriteList is defined as follows.

void MyFrame::WriteList(string mes)
{    
 int nl=logList->GetCount();
 if(nl>=20){ logList->Delete(0); }    
 wxString wstr = mes;
 wxDateTime wdt;
 wdt.SetToCurrent();
 wstr = wdt.Format(wxT("%Y-%m-%d %H:%M:%S"), wxDateTime::Local)+
  " : "+wstr; // add timestamp
 wxArrayString astr;
 astr.Add(wstr);
 nl=logList->GetCount();
 logList->InsertItems(astr,nl);
}


Thereafter, we prepare to keep the old Log target and to replace it with the list box as a new log target. A variable called m_oldLogger is decleared to keep the old one.

wxLog *m_oldLogger;


Then, MyFrame class for GUI is decleared as a derived class of wxLog.

class MyFrame : public wxFrame, private wxLog


And, in the constructor and destructor of MyFrame, defining log target is written as follows.

MyFrame::MyFrame(...) : ...
{
m_oldLogger = wxLog::GetActiveTarget();

... do somethings

wxLog::SetActiveTarget(this);
}
MyFrame::~MyFrame()
{
wxLog::SetActiveTarget(m_oldLogger);
... do somethings
}


A virtual function called DoLogRecord needs to be defined for logging the the derived class MyFrame.

void MyFrame::DoLogRecord(wxLogLevel level, const wxString& msg, const wxLogRecordInfo& info)
{
 this->WriteList(msg.ToStdString());
}


After that, everytime log functions such as wxLogStatus or wxLogError are called, the messages will be displayed on the list box.

Creating Threads

Everytime the 'Start' button is clicked, a new thread is created and kept in wxThread array as an element. For that wxArrayThread is type defined as wxThread array.

WX_DEFINE_ARRAY_PTR(wxThread *, wxArrayThread);


Thereafter, a wxThread array is declared in MyApp class as follow.

wxArrayThread m_threads;


To get access to that module variable in MyApp class, you can use wxGetApp as shown below.

wxArrayThread& threads = wxGetApp().m_threads;


The application is shut down, you can use a bool variable to inform all the thread to close and a semaphore for waiting. For that the following two additional module variables are declared in MyApp.

wxSemaphore m_semAllDone;
bool m_shuttingDown;


As these module variables are used by all the threads, you can protect them using a Mutex or a critical section. Critical section cannot be seen outside of the application and is more efficient. We can declare a critical section as follow.

wxCriticalSection m_critsect;


Whenever the 'Start' button is clicked, an instance of MyThread is created using Create to get a new thread. Before keeping the newly created thread in the module variable thread array, it is locked uisng wxCriticalSectionLocker. When the function return, it is automatically unlocked. Then, the tasks in Run and Entry are executed.

void MyFrame::OnStart(wxCommandEvent& WXUNUSED(event))
{       
 MyThread *thread = CreateThread();  // call the following function
 if ( thread->Run() != wxTHREAD_NO_ERROR )
 {
  wxLogStatus(wxT("Can't start thread!"));
 }
}

MyThread *MyFrame::CreateThread()
{
 MyThread *thread = new MyThread;
 
 if ( thread->Create() != wxTHREAD_NO_ERROR )
 {
  wxLogStatus(wxT("Can't create thread!"));
 }
 
 wxCriticalSectionLocker enter(wxGetApp().m_critsect);
 wxGetApp().m_threads.Add(thread);
 
 return thread;
}




Defining Tasks for Thread

The tasks to be executed for a thread can be defined in virtual Entry. To allow the thread to exit when it is destroyed, the function TestDestroy() needs to be called periodically in the thread. It also needs to check the flag for the application closing signal, it can exit the thread when necessary. An example Entry function is as follows.

wxThread::ExitCode MyThread::Entry()
{
 wxLogMessage("Thread started (priority = %u).", GetPriority());
 
 for ( m_count = 0; m_count < 10; m_count++ )
 {
  // check if the application is shutting down: in this case all threads
  // should stop a.s.a.p.
  {
   wxCriticalSectionLocker locker(wxGetApp().m_critsect);
   if ( wxGetApp().m_shuttingDown )
   return NULL;
  }
  
  // check if just this thread was asked to exit
  if ( TestDestroy() )
  break;
  
  wxLogMessage("Thread progress: %u", m_count);
  
  // wxSleep() can't be called from non-GUI thread!
  wxThread::Sleep(1000);
 }
 
 wxLogMessage("Thread finished.");
 
 return NULL;
}


Deleting a Thread

A normal detached thread is automatically destroyed once its tasks have been executed. If you want to destroy it manually, you can use Delete and TestDestroy() needs to be called in the thread. The following function deletes the last thread in the thread arry when you press the Stop button.

void MyFrame::OnStop(wxCommandEvent& WXUNUSED(event))
{
 wxThread* toDelete = NULL;
 {
  wxCriticalSectionLocker enter(wxGetApp().m_critsect);
  
  // stop the last thread
  if ( wxGetApp().m_threads.IsEmpty() )
  {
   wxLogStatus(wxT("No thread to stop!"));
  }
  else
  {
   toDelete = wxGetApp().m_threads.Last();
  }
 }
 
 if ( toDelete )
 {
  // This can still crash if the thread gets to delete itself
  // in the mean time.
  toDelete->Delete();
  wxLogStatus(wxT("Last thread stopped."), 1);
 }
}


In the destructor of the thread, it is required to remove itself from the thread array which is a module variable. When the application is shutting down and after all the entries from the thread array have been removed, we post the semaphore.

MyThread::~MyThread()
{
 wxCriticalSectionLocker locker(wxGetApp().m_critsect);
 
 wxArrayThread& threads = wxGetApp().m_threads;
 threads.Remove(this);
 
 if ( threads.IsEmpty() )
 {
  // signal the main thread that there are no more threads left if it is
  // waiting for us
  if ( wxGetApp().m_shuttingDown )
  {
   wxGetApp().m_shuttingDown = false;
   
   wxGetApp().m_semAllDone.Post();
  }
 }
}


When MyFrame is closed and there are threads in the thread array, in its destructor, we set a flag to indicate that all threads should exit. And wait for the semaphore as follows.

// NB: although the OS will terminate all the threads anyhow when the main
//     one exits, it's good practice to do it ourselves -- even if it's not
//     completely trivial in this example

// tell all the threads to terminate: note that they can't terminate while
// we're deleting them because they will block in their OnExit() -- this is
// important as otherwise we might access invalid array elements

{
 wxCriticalSectionLocker locker(wxGetApp().m_critsect);
 
 // check if we have any threads running first
 const wxArrayThread& threads = wxGetApp().m_threads;
 size_t count = threads.GetCount();
 
 if ( !count )
 return;
 
 // set the flag indicating that all threads should exit
 wxGetApp().m_shuttingDown = true;
}

// now wait for them to really terminate
wxGetApp().m_semAllDone.Wait();


Pause and Resume

Examples to pause and resume a thread are shown below.

void MyFrame::OnPause(wxCommandEvent& WXUNUSED(event))
{       
 wxCriticalSectionLocker enter(wxGetApp().m_critsect);
 
 // pause last running thread
 int n = wxGetApp().m_threads.Count() - 1;
 while ( n >= 0 && !wxGetApp().m_threads[n]->IsRunning()) n--;
 
 if ( n < 0 ) {
  wxLogStatus(wxT("No thread to pause!"));
 }
 else {
  wxGetApp().m_threads[n]->Pause();
  wxLogStatus(wxT("Thread paused."), 1);
 }
}

void MyFrame::OnResume(wxCommandEvent& WXUNUSED(event))
{       
 wxCriticalSectionLocker enter(wxGetApp().m_critsect);
 
 // resume first suspended thread
 size_t n = 0, count = wxGetApp().m_threads.Count();
 while ( n < count && !wxGetApp().m_threads[n]->IsPaused() )
 n++;
 
 if ( n == count ) {
  wxLogStatus(wxT("No thread to resume!"));
 }
 else {
  wxGetApp().m_threads[n]->Resume();
  wxLogStatus(wxT("Thread resumed."), 1);
 }
}


The complete example can be seen at the following link.

th-simple.cpp https://github.com/yan9a/cewx/blob/master/thread/th-simple/th-simple.cpp

The GUI and the result of running and pausing a thread is shown in Figure 2.


Figure 2. th-simple.cpp


Using Events

As an another example, we will discuss about sending events from a worker thread to the application. The example program for using events can be seen at the following link.

https://github.com/yan9a/cewx/blob/master/thread/th-worker/th-worker.cpp

To send events to MyFrame, we need to add it in the constructor of the thread. Thereafter, a wxThreadEvent is created, an integer value is set using SetInt, and, the event is sent using wxQueueEvent. The code for the constructor and Entry for the thread is shown below.

MyWorkerThread::MyWorkerThread(MyFrame *frame)
: wxThread()
{
 m_frame = frame;
 m_count = 0;
}

wxThread::ExitCode MyWorkerThread::Entry()
{
 for ( m_count = 0; !m_frame->Cancelled() && (m_count < 100); m_count++ )
 {
  // check if we were asked to exit
  if ( TestDestroy() )
  break;
  
  // create any type of command event here
  wxThreadEvent event( wxEVT_THREAD, ID_WORKER_THREAD );
  event.SetInt( m_count );
  
  // send in a thread-safe way
  wxQueueEvent( m_frame, event.Clone() );
  wxMilliSleep(200);
 }
 wxThreadEvent event( wxEVT_THREAD, ID_WORKER_THREAD );
 event.SetInt(-1); // that's all
 wxQueueEvent( m_frame, event.Clone() );
 return NULL;
}


When the Start button is clicked, the thread and a dialog to display the sent values from the thread are created. The dialog displays from 1 to 100, or until it is closed by the user. Then, the thread is terminated and the value -1 is sent using event. The application destroys the dialog when it receives -1. The event handler for the thread called OnWorkerEvent, and adding it in the event table is shown in the following listing.


wxBEGIN_EVENT_TABLE(MyFrame, wxFrame)
...
EVT_THREAD(ID_WORKER_THREAD, MyFrame::OnWorkerEvent)
...
wxEND_EVENT_TABLE()

... 


void MyFrame::OnWorkerEvent(wxThreadEvent& event)
{
 int n = event.GetInt();
 if ( n == -1 )
 {
  m_dlgProgress->Destroy();
  m_dlgProgress = (wxProgressDialog *)NULL;
  
  // the dialog is aborted because the event came from another thread, so
  // we may need to wake up the main event loop for the dialog to be
  // really closed
  wxWakeUpIdle();
 }
 else
 {
  if ( !m_dlgProgress->Update(n) )
  {
   wxCriticalSectionLocker lock(m_csCancelled);
   
   m_cancelled = true;
  }
 }
}


The complete example for using events with thread is at the following link.

th-worker.cpp https://github.com/yan9a/cewx/blob/master/thread/th-worker/th-worker.cpp

The GUI and the result of running the program is shown in Figure 3.


Figure 3. th-worker.cpp




Using GUI from Thread

When using threads in an application, it is better to do GUI tasks only in the main thread. The other threads should only perform communication using events. The following example demonstrates using GUI also from other threads but it is discouraged in the design view point.

th-gui.cpp https://github.com/yan9a/cewx/blob/master/thread/th-gui/th-gui.cpp

A GUI element, image dialot, is used by a GUI thread and it has a pointer for the image dialog.

 class MyGUIThread : public wxThread
 {
  public:
  MyGUIThread(MyImageDialog *dlg) : wxThread(wxTHREAD_JOINABLE) {
   m_dlg = dlg;
  }
  virtual ExitCode Entry();
  
  private:
  MyImageDialog *m_dlg;
 };


Then, in Entry, blue and green rectangles are drawn randomly on the bitmap image of the image dialog. Before using the GUI from the secondary thread, it is locked using wxMutexGuiEnter() and, after finished using the GUI, it is released using wxMutexGuiLeave(). To use the image dialog, to enter into the critical section, wxCriticalSectionLocker is used. An event is sent to the GUI at each step. The Entry() function is shown below.

wxThread::ExitCode MyGUIThread::Entry()
{
 for (int i=0; im_csBmp);
   
   // draw some more stuff on the bitmap
   wxMemoryDC dc(m_dlg->m_bmp);
   dc.SetBrush((i%2)==0 ? *wxBLUE_BRUSH : *wxGREEN_BRUSH);
   dc.DrawRectangle(rand()%GUITHREAD_BMP_SIZE, rand()%GUITHREAD_BMP_SIZE, 30, 30);
   
   // simulate long drawing time:
   wxMilliSleep(200);
  }
  
  // if we don't release the GUI mutex the MyImageDialog won't be able to refresh
  wxMutexGuiLeave();
  
  // notify the dialog that another piece of our masterpiece is complete:
  wxThreadEvent event( wxEVT_THREAD, GUITHREAD_EVENT );
  event.SetInt(i+1);
  wxQueueEvent( m_dlg, event.Clone() );
  
  // give the main thread the time to refresh before we lock the GUI mutex again
  // FIXME: find a better way to do this!
  wxMilliSleep(100);
 }
 return (ExitCode)0;
}


In the image dialog, There are the bitmap image which was just mentioned, a critical section, and a GUI thread. An separate event table is also needed for it.

class MyImageDialog: public wxDialog
{
 public:
 // ctor
 MyImageDialog(wxFrame *frame);
 ~MyImageDialog();
 
 // stuff used by MyGUIThread:
 wxBitmap m_bmp;    // the bitmap drawn by MyGUIThread
 wxCriticalSection m_csBmp;        // protects m_bmp
 
 private:
 void OnGUIThreadEvent(wxThreadEvent& event);
 void OnPaint(wxPaintEvent&);
 
 MyGUIThread m_thread;
 int m_nCurrentProgress;
 
 wxDECLARE_EVENT_TABLE();
};

...


wxBEGIN_EVENT_TABLE(MyImageDialog, wxDialog)
EVT_THREAD(GUITHREAD_EVENT, MyImageDialog::OnGUIThreadEvent)
EVT_PAINT(MyImageDialog::OnPaint)
wxEND_EVENT_TABLE()


In the thread event of the image dialog, the current progress is calculated, and Refresh is called. OnPaint event handler paints the bitmap and draws the progress bar. The complete example can be seen at the following link.

th-gui.cpp https://github.com/yan9a/cewx/blob/master/thread/th-gui/th-gui.cpp

The result of running GUI thread is shown in Figure 4.


Figure 4. th-gui.cpp


References

[Lav98] Guilhem Lavaux, Vadim Zeitlin. wxWidgets thread sample. 1998.
url: https://github.com/wxWidgets/wxWidgets/blob/master/samples/thread/thread.cpp.

No comments:

Post a Comment

Comments are moderated and don't be surprised if your comment does not appear promptly.