Search This Blog

Thursday, January 30, 2020

Multithreading with wxWidgets

C++ program တွေမှာ wxWidgets ကို သုံးပြီး multithreading လုပ်ဆောင်မှု တွေ ပြုလုပ် တဲ့အကြောင်း ဆွေးနွေး ပါမယ်။ ဆွေးနွေးမယ့် နမူနာ ပုံစံ အမျိုးမျိုး က
တို့ ဖြစ်ပါတယ်။

အခြေခံ multithreading နမူနာ နဲ့ သူ့ကို run လိုက်လို့ ရလာတဲ့ ရလဒ် ကို အောက်က link နဲ့ ပုံမှာ ပြထားပါတယ်။

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




Figure 1. thread1.cpp


Simple Multithreading Example

ရိုးရှင်း တဲ့ multithreading နမူနာ အနေနဲ့

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

ဆိုတဲ့ ပရိုဂရမ် လေးကို ဆွေးနွေး ပါမယ်။ ပရိုဂရမ် မှာ start ၊ stop ၊ pause နဲ့ resume ဆိုတဲ့ button လေးခု ကို အရင် ထည့် ပါမယ်။ Start button က thread အသစ် တစ်ခု ကို ဖန်တီး ပြီး၊ စတင် ဖို့ပါ။ Stop က နောက်ဆုံး ဖန်တီး လိုက်တဲ့ thread ကို ရှာပြီး ဖျက်ပေး ပါလိမ့်မယ်။ Pause က နောက်ဆုံး စတင် တဲ့ thread ကို ရှာပြီး ခေတ္တရပ် (pause) လုပ်ပေး ပါတယ်။ Resume က အရင်ဆုံး paused လုပ်ခဲ့ တဲ့ thread ကို ရှာပြီး၊ ပြန် run ပေးဖို့ ပါ။

Log Target ပြောင်းခြင်း

နောက် တစ်ခါ message တွေကို user interface မှာ ဖော်ပြ ဖို့ အတွက် logList လို့ နာမည် ပေးထားတဲ့ List Box တစ်ခုကို ဖန်တီး ပါမယ်။ ဖန်တီး လိုက်တဲ့ List box မှာ message တွေကို timestamp နဲ့ တွဲပြီး log လုပ်ဖို့၊ လိုင်း အရေအတွက် ကန့်သတ်ပြီး အလို အလျောက် ရှင်းလင်း ဖို့ တွေ အတွက် WriteList ဆိုတဲ့ function တစ်ခု ကို အောက်ပါ အတိုင်း ရေးလိုက် ပါတယ်။

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


Log target အဟောင်းကို မှတ်ထား ပြီး၊ ခုနက list box ကို log target အသစ် အနေနဲ့ အစားထိုးဖို့ အတွက် ပြင်ဆင် ပါမယ်။ အဟောင်း ကို သိမ်းဖို့ အတွက် m_oldLogger ဆိုတဲ့ variable တစ်ခု ကို အောက်ပါ အတိုင်း ကြေငြာ ပါမယ်။

wxLog *m_oldLogger;


ပြီးရင် GUI အတွက် ဖြစ်တဲ့ MyFrame က wxLog ကနေ derived လုပ်ယူ ဖို့ လိုပါတယ်။

class MyFrame : public wxFrame, private wxLog


အဲဒီ နောက် MyFrame ရဲ့ constructor နဲ့ destructor တွေမှာ log target သတ်မှတ် မှု တွေကို အောက်ပါ အတိုင်း လုပ်နိုင် ပါတယ်။

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

... do somethings

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


Derived class ဖြစ်တဲ့ MyFrame အတွက် log လုပ်ရင် လုပ်ဆောင်တဲ့ virtual DoLogRecord ဖန်ရှင် ကို အောက်ပါ အတိုင်း သတ်မှတ် ထားဖို့ လည်း လိုပါတယ်။

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


အဲဒါ ဆိုရင် wxLogStatus ၊ wxLogError စတဲ့ ဖန်ရှင် တွေ ခေါ်လိုက် တိုင်း သူတို့ ရဲ့ message တွေက list box မှာ လာပေါ်မှာ ဖြစ်ပါတယ်။

Thread များဖန်တီးခြင်း

Start ခလုတ် ကို တစ်ချက် နှိပ်တိုင်း thread အသစ် တစ်ခု ဖန်တီးပြီး၊ wxThread array ထဲမှာ element တစ်ခု အနေနဲ့ ထပ်ထည့် သိမ်း ပါမယ်။ အဲဒီ အတွက် wxThread array အမျိုးအစား အနေ နဲ့ wxArrayThread ဆိုပြီး type define လုပ်ပါမယ်။

WX_DEFINE_ARRAY_PTR(wxThread *, wxArrayThread);


အဲဒီ နောက် MyApp class ထဲမှာ wxThread array တစ်ခု ကို အောက်ပါ အတိုင်း ကြေငြာ နိုင်ပါပြီ။

wxArrayThread m_threads;


MyApp class ထဲက အဲဒီ module variable ကို access လုပ်ချင်ရင် တော့ အောက်ပါ အတိုင်း ပြုလုပ် နိုင် ပါတယ်။

wxArrayThread& threads = wxGetApp().m_threads;


Application ကို ပိတ်လိုက် တဲ့ အခါ လက်ရှိ လုပ်လက်စ thread တွေ အားလုံး ကို ရပ်ဖို့ bool variable တစ်ခု နဲ့ လှမ်း အကြောင်း ကြား ပြီး semaphore တစ်ခု နဲ့ Wait လုပ်နိုင် ပါတယ်။ အဲဒီ အတွက် အောက်ပါ variable နှစ်ခု ကို MyApp ထဲမှာ module variable အနေနဲ့ ထပ်ကြေငြာ လိုက်ပါမယ်။

wxSemaphore m_semAllDone;
bool m_shuttingDown;


Thread အားလုံး က မျှသုံး ကြမယ့် အဲဒီ module variables တွေကို ကာကွယ် ဖို့ အတွက် Mutex ဒါမှ မဟုတ် critical section ကို သုံးနိုင် ပါတယ်။ Critical section က application အပြင် ကနေ မမြင် ရပဲ၊ နဲနဲ ပိုပြီး efficient ဖြစ်တဲ့ အတွက် သူ့ကို သုံးဖို့ အောက်ပါ အတိုင်း ကြေငြာ လိုက်ပါမယ်။

wxCriticalSection m_critsect;


Start ခလုတ် ကို နှိပ်လိုက် တိုင်း MyThread ရဲ့ instance တစ်ခု ကို ဖန်တီး ပြီး၊ Create ကို ခေါ်ပြီး thread တစ်ခု ဖန်တီး ပါမယ်။ ဖန်တီး လို့ ရတဲ့ thread ကို module variable ဖြစ်တဲ့ thread array ထဲမှာ ထပ်ထည့် မသိမ်းခင် wxCriticalSectionLocker နဲ့ အရင် lock လုပ်ပါမယ်။ အဲဒီ ဖန်ရှင် က return တာနဲ့ အလိုလို unlock ပြန်ဖြစ် သွားမှာ ပါ။ ပြီးတဲ့ အခါ Run နဲ့ Entry ထဲက လုပ်ငန်း တွေကို စတင် ပါတယ်။

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




Thread လုပ်ငန်းများသတ်မှတ်ခြင်း

Thread တစ်ခု ထဲမှာ လုပ်ဆောင် မယ့် လုပ်ငန်း တွေကို virtual Entry ထဲမှာ သတ်မှတ် နိုင် ပါတယ်။ သူ့ကို Delete လုပ်ရင် ထွက်နိုင် အောင် TestDestroy() ဆိုတဲ့ ဖန်ရှင်ကို thread ထဲမှာ ပုံမှန် ခေါ်ပေး နေဖို့ လည်း လိုအပ် ပါတယ်။ Application က ပိတ်လိုက် တဲ့ အကြောင်း signal လုပ်တဲ့ flag ကို လည်း ပုံမှန် စစ်ပြီး လိုအပ် ရင် ချက်ချင်း exit လုပ်နိုင် ပါတယ်။ နမူနာ Entry ဖန်ရှင် တစ်ခု ကို အောက်မှာ တွေ့နိုင် ပါတယ်။

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


Thread ကို ဖျက်ခြင်း

ပုံမှန် detached thread တစ်ခု က သူ့ရဲ့ လုပ်ငန်း ပြီးပြီး သွား တာနဲ့ အလို အလျောက် destroy ဖြစ်သွား ပါတယ်။ အကယ်၍ မပြီး ခင် ဖျက်ချင် ရင်တော့ Delete နဲ့ ဖျက်နိုင် ပြီး၊ thread ထဲမှာ လည်း TestDestroy() ကို ခေါ်ဖို့ လိုပါတယ်။ Stop ခလုတ် ကို နှိပ်ရင် thread array ထဲက နောက်ဆုံး thread ကို delete လုပ်ပေး တဲ့ လုပ်ပေး တဲ့ ဖန်ရှင် ကို အောက်ပါ အတိုင်း တွေ့နိုင် ပါတယ်။

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


ဖျက်ပြီး တဲ့ အခါ thread ရဲ့ destructor ထဲမှာ module variable ဖြစ်တဲ့ thread array ထဲက နေ သူ့ဟာသူ ပြန်ဖယ် ဖို့ အောက်ပါ အတိုင်း ပြုလုပ် နိုင် ပါတယ်။ Application က ပိတ်နေ တဲ့ အခါ thread array ထဲမှာ လည်း အကုန် ဖယ်ပြီးတဲ့ အခါ semaphore ကို Post လုပ် ပါမယ်။

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


MyFrame ရဲ့ destructor ထဲမှာ လည်း သူ့ကို ပိတ်လိုက် တဲ့ အခါ thread array ထဲမှာ လည်း thread တွေ ရှိနေ သေးရင် flag ပြပြီး၊ semaphore ကို အောက်ပါ အတိုင်း စောင့် ပါမယ်။

// 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 နှင့် Resume

Thread ကို ခေတ္တ ရပ်ဖို့ အတွက် pause လုပ်တာ နဲ့ ပြန်စဖို့ resume လုပ်တဲ့ နမူနာ တွေကို အောက်ပါ အတိုင်း တွေ့နိုင် ပါတယ်။

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


နမူနာ တစ်ပုဒ် လုံး အပြည့် အစုံ ကိုတော့

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

မှာ ဖော်ပြ ထားပါတယ်။ ပရိုဂရမ် ရဲ့ GUI နဲ့ thread ကို run ပြီး၊ ပြန်ရပ် လိုက်တဲ့ အခါ ရလာတဲ့ ရလဒ် ကို Figure 2 မှာ တွေ့နိုင် ပါတယ်။


Figure 2. th-simple.cpp ၏ ရလဒ်။


Event များသုံးခြင်း

နောက်ထပ် နမူနာ တစ်ခု အနေနဲ့ worker thread တစ်ခု ကနေ application ကို event တွေ လှမ်းပို့ အကြောင်း ကြား တဲ့

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

ဆိုတဲ့ ပရိုဂရမ် ကို ဆွေးနွေး ပါမယ်။ MyFrame ကို event တွေလှမ်းပို့ ဖို့ thread ရဲ့ constructor မှာ MyFrame ကို ထည့်ပေး ရပါမယ်။ ပြီးတဲ့ အခါ wxThreadEvent တစ်ခု ဖန်တီး၊ SetInt နဲ့ တန်ဖိုး တစ်ခု ကို ထည့်ပေး ပြီး၊ wxQueueEvent နဲ့ event ကို ပို့နိုင် ပါတယ်။ အဲဒီ လို thread ရဲ့ constructor နဲ့ Entry နမူနာ ကို အောက်မှာ တွေ့နိုင် ပါတယ်။

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


Start ဆိုတဲ့ ခလုတ် ကို နှိပ်လိုက် တာနဲ့ အဲဒီ thread ကို ဖန်တီး၊ စတင်ပြီး သူ့က ပို့ပေး တဲ့ တန်ဖိုး တွေကို ဖော်ပြဖို့ dialog တစ်ခု ကို ဖန်တီး လိုက် ပါမယ်။ Dialog မှာ ၁ ကနေ ၁၀၀ ထိ တန်ဖိုး တွေကို ဖော်ပြ ပေးမှာ ဖြစ်ပြီး၊ ပြီးသွားရင် ဒါမှမဟုတ် dialog ကို ပိတ်လိုက် ရင် thread ကို အဆုံး သတ်ပြီး၊ အနုတ် ၁ တန်ဖိုး ကို event နဲ့ ပို့ပေး ပါမယ်။ Application ဘက်မှာ လည်း အနုတ် ၁ ရလာ ရင် dialog ကို destroy လုပ်လိုက် ပါမယ်။ Thread ရဲ့ event ကို handle လုပ်မယ့် OnWorkerEvent လို့ နာမည် ပေးလိုက် တဲ့ ဖန်ရှင် ကို event table မှာ ထည့်သွင်း တာရယ်၊ သူ့ရဲ့ လုပ်ဆောင်မှု တွေကို အောက်မှာ ပြထား ပါတယ်။


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


ပရိုဂရမ် တစ်ပုဒ် လုံး ကို အောက်က link မှာ ဖော်ပြ ထားပါတယ်။

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

ပရိုဂရမ် ရဲ့ GUI နဲ့ thread ကို run လိုက်တဲ့ အခါ ပေါ်လာမယ့် dialog ကို Figure. 3 မှာ တွေ့နိုင် ပါတယ်။


Figure 3. th-worker.cpp ၏ ရလဒ်။




Thread မှ GUI ကိုလှမ်းသုံးခြင်း

Application ထဲမှာ thread တွေကို သုံးမယ် ဆိုရင် main thread ကပဲ GUI လုပ်ဆောင်မှု တွေကို လုပ်ဆောင်ပြီး ကျန်တဲ့ thread တွေနဲ့ event တွေက ပဲ ဆက်သွယ်တာ ကောင်းပါတယ်။ ဒီ နမူနာ

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

မှာ တော့ တစ်ခြား thread တွေက နေလည်း GUI ကို လှမ်း သုံးလို့ ရတယ် ဆိုတဲ့ အကြောင်း လုပ်ပြရုံ သက်သက် ဖြစ်ပြီး၊ ဒီဇိုင်း အရ မကောင်း တာကြောင့် အဲဒီ လို သုံးဖို့ ကို အား မပေး ပါဘူး။ GUI element တစ်ခု ဖြစ်တဲ့ image dialog ကို လှမ်းပြီး အသုံးပြု မယ့် gui thread တစ်ခု ကို အောက်ပါ အတိုင်း သတ်မှတ်နိုင် ပါတယ်။ သူလှမ်းပြီး ထိန်းချုပ်မယ့် image dialog အတွက် လည်း pointer တစ်ခု ပါဝင် ပါမယ်။

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

  private:
  MyImageDialog *m_dlg;
 };


အဲဒီ နောက် သူ့ရဲ့ လုပ်ဆောင် မှု တွေ ဖြစ်တဲ့ Entry မှာ image dialog ပေါ်က bitmap image ပေါ်မှာ rectangle တွေကို ကြုံရာ ကျပန်း နေရာ တွေမှာ အပြာ၊ အစိမ်း တစ်လှည့်စီ ဆွဲမှာ ဖြစ်ပါတယ်။ GUI ကို secondary ဖြစ်တဲ့ thread ထဲက နေ လှမ်း မသုံးခင် မှာ wxMutexGuiEnter(); နဲ့ lock လုပ်ပြီး၊ ပြီးသွားတဲ့ အခါ wxMutexGuiLeave(); နဲ့ release လုပ် ပါမယ်။ Image dialog ကို သုံးဖို့ အတွက် လည်း သူ့ကို ကာကွယ် ထားတဲ့ critical section ကို ဝင်ဖို့ wxCriticalSectionLocker သုံး ပါတယ်။ တစ်ဆင့် ပြီးတဲ့ အခါတိုင်း image dialog ကို event ပို့ပေး ပါတယ်။ Entry ဖန်ရှင် ကို အောက်မှာ ဖော်ပြ ထားပါတယ်။

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


Image dialog မှာတော့ ခုန က ပြောခဲ့ တဲ့ bitmap image တစ်ခုရယ်၊ သူ့ကို ကာကွယ်ဖို့ critical section တစ်ခု ရယ်၊ gui thread ရယ် အဓိက ပါဝင် ပါတယ်။ ပြီးရင် သူ့ အတွက် event table တစ်ခု သပ်သပ် ထပ် ကြေငြာ ဖို့ လည်း လိုပါတယ်။

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()


Image dialog ရဲ့ thread event မှာ လက်ရှိ progress ကို ပဲ တွက်ပြီး၊ Refresh ကို ခေါ်ပေး ပါတယ်။ အဲဒီ နောက် OnPaint event handler မှာ bitmap ကို paint လုပ်ပေး ပြီး၊ prograss bar ဆွဲပြ ပါတယ်။ ပရိုဂရမ် အပြည့် အစုံကို

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

မှာ ဖော်ပြ ထားပါတယ်။ ပရိုဂရမ် ရဲ့ GUI နဲ့ thread ကို run လိုက်တဲ့ အခါ ရလာတဲ့ ရလဒ် ကို 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