X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/74d60f66eeeeeb385beb7e466e03e5cee7b690b1..14619f10b0bdb630206607abd0ce0319d45e095a:/samples/thread/thread.cpp diff --git a/samples/thread/thread.cpp b/samples/thread/thread.cpp index f5ec942d81..7948c35a47 100644 --- a/samples/thread/thread.cpp +++ b/samples/thread/thread.cpp @@ -5,10 +5,18 @@ // Modified by: // Created: 06/16/98 // RCS-ID: $Id$ -// Copyright: (c) 1998-2002 wxWidgets team -// Licence: wxWindows license +// Copyright: (c) 1998-2009 wxWidgets team +// Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// +// ============================================================================ +// declarations +// ============================================================================ + +// ---------------------------------------------------------------------------- +// headers +// ---------------------------------------------------------------------------- + // For compilers that support precompilation, includes "wx/wx.h". #include "wx/wxprec.h" @@ -27,11 +35,18 @@ #include "wx/thread.h" #include "wx/dynarray.h" #include "wx/numdlg.h" - #include "wx/progdlg.h" +// ---------------------------------------------------------------------------- +// resources +// ---------------------------------------------------------------------------- + #include "../sample.xpm" +// ---------------------------------------------------------------------------- +// private classes +// ---------------------------------------------------------------------------- + // define this to use wxExecute in the exec tests, otherwise just use system #define USE_EXECUTE @@ -44,7 +59,10 @@ class MyThread; WX_DEFINE_ARRAY_PTR(wxThread *, wxArrayThread); -// Define a new application type +// ---------------------------------------------------------------------------- +// the application object +// ---------------------------------------------------------------------------- + class MyApp : public wxApp { public: @@ -67,34 +85,30 @@ public: bool m_shuttingDown; }; -// Create a new application object -IMPLEMENT_APP(MyApp) +// ---------------------------------------------------------------------------- +// the main application frame +// ---------------------------------------------------------------------------- -// Define a new frame type -class MyFrame: public wxFrame +class MyFrame : public wxFrame, + private wxLog { public: // ctor - MyFrame(wxFrame *frame, const wxString& title, int x, int y, int w, int h); + MyFrame(const wxString& title); virtual ~MyFrame(); - // this function is MT-safe, i.e. it can be called from worker threads - // safely without any additional locking - void LogThreadMessage(const wxString& text) - { - wxCriticalSectionLocker lock(m_csMessages); - m_messages.push_back(text); - - // as we effectively log the messages from the idle event handler, - // ensure it's going to be called now that we have some messages to log - wxWakeUpIdle(); - } - // accessors for MyWorkerThread (called in its context!) bool Cancelled(); +protected: + virtual void DoLogRecord(wxLogLevel level, + const wxString& msg, + const wxLogRecordInfo& info); + private: // event handlers + // -------------- + void OnQuit(wxCommandEvent& event); void OnClear(wxCommandEvent& event); @@ -105,16 +119,26 @@ private: void OnResumeThread(wxCommandEvent& event); void OnStartWorker(wxCommandEvent& event); - void OnWorkerEvent(wxThreadEvent& event); - void OnUpdateWorker(wxUpdateUIEvent& event); - void OnExecMain(wxCommandEvent& event); - void OnExecThread(wxCommandEvent& event); + void OnStartGUIThread(wxCommandEvent& event); void OnShowCPUs(wxCommandEvent& event); void OnAbout(wxCommandEvent& event); void OnIdle(wxIdleEvent &event); + void OnWorkerEvent(wxThreadEvent& event); + void OnUpdateWorker(wxUpdateUIEvent& event); + + + // logging helper + void DoLogLine(wxTextCtrl *text, + const wxString& timestr, + const wxString& threadstr, + const wxString& msg); + + + // thread helper functions + // ----------------------- // helper function - creates a new thread (but doesn't run it) MyThread *CreateThread(); @@ -122,13 +146,17 @@ private: // update display in our status bar: called during idle handling void UpdateThreadStatus(); - // log the messages queued by LogThreadMessage() - void DoLogThreadMessages(); + // internal variables + // ------------------ // just some place to put our messages in wxTextCtrl *m_txtctrl; + // old log target, we replace it with one using m_txtctrl during this + // frame life time + wxLog *m_oldLogger; + // the array of pending messages to be displayed and the critical section // protecting it wxArrayString m_messages; @@ -143,13 +171,15 @@ private: // was the worker thread cancelled by user? bool m_cancelled; - - // protects m_cancelled - wxCriticalSection m_critsectWork; + wxCriticalSection m_csCancelled; // protects m_cancelled DECLARE_EVENT_TABLE() }; +// ---------------------------------------------------------------------------- +// constants +// ---------------------------------------------------------------------------- + // ID for the menu commands enum { @@ -162,106 +192,36 @@ enum THREAD_STOP_THREAD, THREAD_PAUSE_THREAD, THREAD_RESUME_THREAD, - THREAD_START_WORKER, + THREAD_START_WORKER, THREAD_EXEC_MAIN, - THREAD_EXEC_THREAD, + THREAD_START_GUI_THREAD, THREAD_SHOWCPUS, - WORKER_EVENT = wxID_HIGHEST+1 // this one gets sent from the worker thread + WORKER_EVENT = wxID_HIGHEST+1, // this one gets sent from MyWorkerThread + GUITHREAD_EVENT // this one gets sent from MyGUIThread }; // ---------------------------------------------------------------------------- -// GUI thread +// a simple thread // ---------------------------------------------------------------------------- class MyThread : public wxThread { public: - MyThread(MyFrame *frame); + MyThread(); virtual ~MyThread(); // thread execution starts here virtual void *Entry(); - // write something to the text control in the main frame - void WriteText(const wxString& text) - { - m_frame->LogThreadMessage(text); - } - public: unsigned m_count; - MyFrame *m_frame; }; -MyThread::MyThread(MyFrame *frame) - : wxThread() -{ - m_count = 0; - m_frame = frame; -} - -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(); - } - } -} - -void *MyThread::Entry() -{ - wxString text; - - text.Printf(wxT("Thread 0x%lx started (priority = %u).\n"), - GetId(), GetPriority()); - WriteText(text); - // wxLogMessage(text); -- test wxLog thread safeness - - 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; - - text.Printf(wxT("[%u] Thread 0x%lx here.\n"), m_count, GetId()); - WriteText(text); - - // wxSleep() can't be called from non-GUI thread! - wxThread::Sleep(1000); - } - - text.Printf(wxT("Thread 0x%lx finished.\n"), GetId()); - WriteText(text); - // wxLogMessage(text); -- test wxLog thread safeness - - return NULL; -} - // ---------------------------------------------------------------------------- -// worker thread +// a worker thread // ---------------------------------------------------------------------------- class MyWorkerThread : public wxThread @@ -281,81 +241,87 @@ public: unsigned m_count; }; -MyWorkerThread::MyWorkerThread(MyFrame *frame) - : wxThread() -{ - m_frame = frame; - m_count = 0; -} - -void MyWorkerThread::OnExit() -{ -} +// ---------------------------------------------------------------------------- +// a thread which executes GUI calls using wxMutexGuiEnter/Leave +// ---------------------------------------------------------------------------- -#define TEST_YIELD_RACE_CONDITION 0 +#define GUITHREAD_BMP_SIZE 300 +#define GUITHREAD_NUM_UPDATES 50 +class MyImageDialog; -void *MyWorkerThread::Entry() +class MyGUIThread : public wxThread { -#if TEST_YIELD_RACE_CONDITION - if ( TestDestroy() ) - return NULL; +public: + MyGUIThread(MyImageDialog *dlg) : wxThread(wxTHREAD_JOINABLE) + { + m_dlg = dlg; + } - wxThreadEvent event( wxEVT_COMMAND_THREAD, WORKER_EVENT ); + virtual ExitCode Entry(); - event.SetInt( 50 ); - wxQueueEvent( m_frame, new wxThreadEvent(event) ); +private: + MyImageDialog *m_dlg; +}; - event.SetInt(-1); - wxQueueEvent( m_frame, new wxThreadEvent(event) ); -#else - for ( m_count = 0; !m_frame->Cancelled() && (m_count < 100); m_count++ ) - { - // check if we were asked to exit - if ( TestDestroy() ) - break; +// ---------------------------------------------------------------------------- +// an helper dialog used by MyFrame::OnStartGUIThread +// ---------------------------------------------------------------------------- - // create any type of command event here - wxThreadEvent event( wxEVT_COMMAND_THREAD, WORKER_EVENT ); - event.SetInt( m_count ); +class MyImageDialog: public wxDialog +{ +public: + // ctor + MyImageDialog(wxFrame *frame); + ~MyImageDialog(); - // send in a thread-safe way - wxQueueEvent( m_frame, new wxThreadEvent(event) ); + // stuff used by MyGUIThread: + wxBitmap m_bmp; // the bitmap drawn by MyGUIThread + wxCriticalSection m_csBmp; // protects m_bmp - wxMilliSleep(200); - } +private: + void OnGUIThreadEvent(wxThreadEvent& event); + void OnPaint(wxPaintEvent&); - wxThreadEvent event( wxEVT_COMMAND_THREAD, WORKER_EVENT ); - event.SetInt(-1); // that's all - wxQueueEvent( m_frame, new wxThreadEvent(event) ); -#endif + MyGUIThread m_thread; + int m_nCurrentProgress; - return NULL; -} + DECLARE_EVENT_TABLE() +}; + +// ============================================================================ +// implementation +// ============================================================================ // ---------------------------------------------------------------------------- -// a thread which simply calls wxExecute +// the application class // ---------------------------------------------------------------------------- -class MyExecThread : public wxThread +// Create a new application object +IMPLEMENT_APP(MyApp) + +MyApp::MyApp() { -public: - MyExecThread(const wxChar *command) : wxThread(wxTHREAD_JOINABLE), - m_command(command) - { - Create(); - } + m_shuttingDown = false; +} - virtual ExitCode Entry() - { - return wxUIntToPtr(EXEC(m_command)); - } +// `Main program' equivalent, creating windows and returning main app frame +bool MyApp::OnInit() +{ + if ( !wxApp::OnInit() ) + return false; -private: - wxString m_command; -}; + // uncomment this to get some debugging messages from the trace code + // on the console (or just set WXTRACE env variable to include "thread") + wxLog::AddTraceMask("thread"); + + // Create the main frame window + new MyFrame("wxWidgets threads sample"); + + return true; +} // ---------------------------------------------------------------------------- -// implementation +// MyFrame // ---------------------------------------------------------------------------- BEGIN_EVENT_TABLE(MyFrame, wxFrame) @@ -367,101 +333,97 @@ BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_MENU(THREAD_PAUSE_THREAD, MyFrame::OnPauseThread) EVT_MENU(THREAD_RESUME_THREAD, MyFrame::OnResumeThread) + EVT_MENU(THREAD_START_WORKER, MyFrame::OnStartWorker) EVT_MENU(THREAD_EXEC_MAIN, MyFrame::OnExecMain) - EVT_MENU(THREAD_EXEC_THREAD, MyFrame::OnExecThread) + EVT_MENU(THREAD_START_GUI_THREAD, MyFrame::OnStartGUIThread) EVT_MENU(THREAD_SHOWCPUS, MyFrame::OnShowCPUs) EVT_MENU(THREAD_ABOUT, MyFrame::OnAbout) EVT_UPDATE_UI(THREAD_START_WORKER, MyFrame::OnUpdateWorker) - EVT_MENU(THREAD_START_WORKER, MyFrame::OnStartWorker) - EVT_THREAD(WORKER_EVENT, MyFrame::OnWorkerEvent) - EVT_IDLE(MyFrame::OnIdle) END_EVENT_TABLE() -MyApp::MyApp() -{ - m_shuttingDown = false; -} - -// `Main program' equivalent, creating windows and returning main app frame -bool MyApp::OnInit() +// My frame constructor +MyFrame::MyFrame(const wxString& title) + : wxFrame(NULL, wxID_ANY, title) { - if ( !wxApp::OnInit() ) - return false; + m_oldLogger = wxLog::GetActiveTarget(); - // uncomment this to get some debugging messages from the trace code - // on the console (or just set WXTRACE env variable to include "thread") - //wxLog::AddTraceMask("thread"); - - // Create the main frame window - MyFrame *frame = new MyFrame((wxFrame *)NULL, _T("wxWidgets threads sample"), - 50, 50, 450, 340); + SetIcon(wxICON(sample)); // Make a menubar wxMenuBar *menuBar = new wxMenuBar; wxMenu *menuFile = new wxMenu; - menuFile->Append(THREAD_CLEAR, _T("&Clear log\tCtrl-L")); + menuFile->Append(THREAD_CLEAR, wxT("&Clear log\tCtrl-L")); menuFile->AppendSeparator(); - menuFile->Append(THREAD_QUIT, _T("E&xit\tAlt-X")); - menuBar->Append(menuFile, _T("&File")); + menuFile->Append(THREAD_QUIT, wxT("E&xit\tAlt-X")); + menuBar->Append(menuFile, wxT("&File")); wxMenu *menuThread = new wxMenu; - menuThread->Append(THREAD_START_THREAD, _T("&Start a new thread\tCtrl-N")); - menuThread->Append(THREAD_START_THREADS, _T("Start &many threads at once")); - menuThread->Append(THREAD_STOP_THREAD, _T("S&top a running thread\tCtrl-S")); + menuThread->Append(THREAD_START_THREAD, wxT("&Start a new thread\tCtrl-N")); + menuThread->Append(THREAD_START_THREADS, wxT("Start &many threads at once")); + menuThread->Append(THREAD_STOP_THREAD, wxT("S&top the last spawned thread\tCtrl-S")); menuThread->AppendSeparator(); - menuThread->Append(THREAD_PAUSE_THREAD, _T("&Pause a running thread\tCtrl-P")); - menuThread->Append(THREAD_RESUME_THREAD, _T("&Resume suspended thread\tCtrl-R")); + menuThread->Append(THREAD_PAUSE_THREAD, wxT("&Pause the last spawned running thread\tCtrl-P")); + menuThread->Append(THREAD_RESUME_THREAD, wxT("&Resume the first suspended thread\tCtrl-R")); menuThread->AppendSeparator(); - menuThread->Append(THREAD_START_WORKER, _T("Start &worker thread\tCtrl-W")); - menuBar->Append(menuThread, _T("&Thread")); - - wxMenu *menuExec = new wxMenu; - menuExec->Append(THREAD_EXEC_MAIN, _T("&Launch a program from main thread\tF5")); - menuExec->Append(THREAD_EXEC_THREAD, _T("L&aunch a program from a thread\tCtrl-F5")); - menuBar->Append(menuExec, _T("&Execute")); + menuThread->Append(THREAD_START_WORKER, wxT("Start a &worker thread\tCtrl-W")); + menuThread->Append(THREAD_EXEC_MAIN, wxT("&Launch a program from main thread\tF5")); + menuThread->Append(THREAD_START_GUI_THREAD, wxT("Launch a &GUI thread\tF6")); + menuBar->Append(menuThread, wxT("&Thread")); wxMenu *menuHelp = new wxMenu; - menuHelp->Append(THREAD_SHOWCPUS, _T("&Show CPU count")); + menuHelp->Append(THREAD_SHOWCPUS, wxT("&Show CPU count")); menuHelp->AppendSeparator(); - menuHelp->Append(THREAD_ABOUT, _T("&About...")); - menuBar->Append(menuHelp, _T("&Help")); - - frame->SetMenuBar(menuBar); + menuHelp->Append(THREAD_ABOUT, wxT("&About")); + menuBar->Append(menuHelp, wxT("&Help")); - // Show the frame - frame->Show(true); - - SetTopWindow(frame); - - return true; -} - -// My frame constructor -MyFrame::MyFrame(wxFrame *frame, const wxString& title, - int x, int y, int w, int h) - : wxFrame(frame, wxID_ANY, title, wxPoint(x, y), wxSize(w, h)) -{ - SetIcon(wxIcon(sample_xpm)); + SetMenuBar(menuBar); m_nRunning = m_nCount = 0; - m_dlgProgress = (wxProgressDialog *)NULL; + m_dlgProgress = NULL; #if wxUSE_STATUSBAR CreateStatusBar(2); #endif // wxUSE_STATUSBAR - m_txtctrl = new wxTextCtrl(this, wxID_ANY, _T(""), wxPoint(0, 0), wxSize(0, 0), + // create the logging text control and a header showing the meaning of the + // different columns + wxTextCtrl *header = new wxTextCtrl(this, wxID_ANY, "", + wxDefaultPosition, wxDefaultSize, + wxTE_READONLY); + DoLogLine(header, " Time", " Thread", "Message"); + m_txtctrl = new wxTextCtrl(this, wxID_ANY, "", + wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY); + wxLog::SetActiveTarget(this); + + // use fixed width font to align output in nice columns + wxFont font(wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_TELETYPE, + wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL); + header->SetFont(font); + m_txtctrl->SetFont(font); + + m_txtctrl->SetFocus(); + + // layout and show the frame + wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(header, wxSizerFlags().Expand()); + sizer->Add(m_txtctrl, wxSizerFlags(1).Expand()); + SetSizer(sizer); + + SetSize(600, 350); + Show(); } MyFrame::~MyFrame() { + wxLog::SetActiveTarget(m_oldLogger); + // 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 @@ -488,9 +450,43 @@ MyFrame::~MyFrame() wxGetApp().m_semAllDone.Wait(); } +void +MyFrame::DoLogLine(wxTextCtrl *text, + const wxString& timestr, + const wxString& threadstr, + const wxString& msg) +{ + text->AppendText(wxString::Format("%9s %10s %s", timestr, threadstr, msg)); +} + +void +MyFrame::DoLogRecord(wxLogLevel level, + const wxString& msg, + const wxLogRecordInfo& info) +{ + // let the default GUI logger treat warnings and errors as they should be + // more noticeable than just another line in the log window and also trace + // messages as there may be too many of them + if ( level <= wxLOG_Warning || level == wxLOG_Trace ) + { + m_oldLogger->LogRecord(level, msg, info); + return; + } + + DoLogLine + ( + m_txtctrl, + wxDateTime(info.timestamp).FormatISOTime(), + info.threadId == wxThread::GetMainId() + ? wxString("main") + : wxString::Format("%lx", info.threadId), + msg + "\n" + ); +} + MyThread *MyFrame::CreateThread() { - MyThread *thread = new MyThread(this); + MyThread *thread = new MyThread; if ( thread->Create() != wxTHREAD_NO_ERROR ) { @@ -503,12 +499,46 @@ MyThread *MyFrame::CreateThread() return thread; } +void MyFrame::UpdateThreadStatus() +{ + wxCriticalSectionLocker enter(wxGetApp().m_critsect); + + // update the counts of running/total threads + size_t nRunning = 0, + nCount = wxGetApp().m_threads.Count(); + for ( size_t n = 0; n < nCount; n++ ) + { + if ( wxGetApp().m_threads[n]->IsRunning() ) + nRunning++; + } + + if ( nCount != m_nCount || nRunning != m_nRunning ) + { + m_nRunning = nRunning; + m_nCount = nCount; + + wxLogStatus(this, wxT("%u threads total, %u running."), unsigned(nCount), unsigned(nRunning)); + } + //else: avoid flicker - don't print anything +} + +bool MyFrame::Cancelled() +{ + wxCriticalSectionLocker lock(m_csCancelled); + + return m_cancelled; +} + +// ---------------------------------------------------------------------------- +// MyFrame - event handlers +// ---------------------------------------------------------------------------- + void MyFrame::OnStartThreads(wxCommandEvent& WXUNUSED(event) ) { static long s_num; - s_num = wxGetNumberFromUser(_T("How many threads to start: "), _T(""), - _T("wxThread sample"), s_num, 1, 10000, this); + s_num = wxGetNumberFromUser(wxT("How many threads to start: "), wxT(""), + wxT("wxThread sample"), s_num, 1, 10000, this); if ( s_num == -1 ) { s_num = 10; @@ -529,11 +559,11 @@ void MyFrame::OnStartThreads(wxCommandEvent& WXUNUSED(event) ) // have the lowest priority, the second - the highest, all the rest // the normal one if ( n == 0 ) - thr->SetPriority(WXTHREAD_MIN_PRIORITY); + thr->SetPriority(wxPRIORITY_MIN); else if ( n == 1 ) - thr->SetPriority(WXTHREAD_MAX_PRIORITY); + thr->SetPriority(wxPRIORITY_MAX); else - thr->SetPriority(WXTHREAD_DEFAULT_PRIORITY); + thr->SetPriority(wxPRIORITY_DEFAULT); threads.Add(thr); } @@ -561,12 +591,14 @@ void MyFrame::OnStartThread(wxCommandEvent& WXUNUSED(event) ) } #if wxUSE_STATUSBAR - SetStatusText(_T("New thread started."), 1); + SetStatusText(wxT("New thread started."), 1); #endif // wxUSE_STATUSBAR } void MyFrame::OnStopThread(wxCommandEvent& WXUNUSED(event) ) { + wxThread* toDelete = NULL; + { wxCriticalSectionLocker enter(wxGetApp().m_critsect); // stop the last thread @@ -576,10 +608,18 @@ void MyFrame::OnStopThread(wxCommandEvent& WXUNUSED(event) ) } else { - wxGetApp().m_threads.Last()->Delete(); + toDelete = wxGetApp().m_threads.Last(); + } + } + + if ( toDelete ) + { + // This can still crash if the thread gets to delete itself + // in the mean time. + toDelete->Delete(); #if wxUSE_STATUSBAR - SetStatusText(_T("Last thread stopped."), 1); + SetStatusText(wxT("Last thread stopped."), 1); #endif // wxUSE_STATUSBAR } } @@ -602,7 +642,7 @@ void MyFrame::OnResumeThread(wxCommandEvent& WXUNUSED(event) ) wxGetApp().m_threads[n]->Resume(); #if wxUSE_STATUSBAR - SetStatusText(_T("Thread resumed."), 1); + SetStatusText(wxT("Thread resumed."), 1); #endif // wxUSE_STATUSBAR } } @@ -625,56 +665,18 @@ void MyFrame::OnPauseThread(wxCommandEvent& WXUNUSED(event) ) wxGetApp().m_threads[n]->Pause(); #if wxUSE_STATUSBAR - SetStatusText(_T("Thread paused."), 1); + SetStatusText(wxT("Thread paused."), 1); #endif // wxUSE_STATUSBAR } } void MyFrame::OnIdle(wxIdleEvent& event) { - DoLogThreadMessages(); - UpdateThreadStatus(); event.Skip(); } -void MyFrame::DoLogThreadMessages() -{ - wxCriticalSectionLocker lock(m_csMessages); - - const size_t count = m_messages.size(); - for ( size_t n = 0; n < count; n++ ) - { - m_txtctrl->AppendText(m_messages[n]); - } - - m_messages.clear(); -} - -void MyFrame::UpdateThreadStatus() -{ - wxCriticalSectionLocker enter(wxGetApp().m_critsect); - - // update the counts of running/total threads - size_t nRunning = 0, - nCount = wxGetApp().m_threads.Count(); - for ( size_t n = 0; n < nCount; n++ ) - { - if ( wxGetApp().m_threads[n]->IsRunning() ) - nRunning++; - } - - if ( nCount != m_nCount || nRunning != m_nRunning ) - { - m_nRunning = nRunning; - m_nCount = nCount; - - wxLogStatus(this, wxT("%u threads total, %u running."), unsigned(nCount), unsigned(nRunning)); - } - //else: avoid flicker - don't print anything -} - void MyFrame::OnQuit(wxCommandEvent& WXUNUSED(event) ) { Close(true); @@ -682,17 +684,19 @@ void MyFrame::OnQuit(wxCommandEvent& WXUNUSED(event) ) void MyFrame::OnExecMain(wxCommandEvent& WXUNUSED(event)) { - wxLogMessage(wxT("The exit code from the main program is %ld"), - EXEC(_T("/bin/echo \"main program\""))); -} - -void MyFrame::OnExecThread(wxCommandEvent& WXUNUSED(event)) -{ - MyExecThread thread(wxT("/bin/echo \"child thread\"")); - thread.Run(); + wxString cmd = wxGetTextFromUser("Please enter the command to execute", + "Enter command", +#ifdef __WXMSW__ + "notepad", +#else + "/bin/echo \"Message from another process\"", +#endif + this); + if (cmd.IsEmpty()) + return; // user clicked cancel - wxLogMessage(wxT("The exit code from a child thread is %ld"), - (long)wxPtrToUInt(thread.Wait())); + wxLogMessage(wxT("The exit code from the main program is %ld"), + EXEC(cmd)); } void MyFrame::OnShowCPUs(wxCommandEvent& WXUNUSED(event)) @@ -703,15 +707,15 @@ void MyFrame::OnShowCPUs(wxCommandEvent& WXUNUSED(event)) switch ( nCPUs ) { case -1: - msg = _T("Unknown number of CPUs"); + msg = wxT("Unknown number of CPUs"); break; case 0: - msg = _T("WARNING: you're running without any CPUs!"); + msg = wxT("WARNING: you're running without any CPUs!"); break; case 1: - msg = _T("This system only has one CPU."); + msg = wxT("This system only has one CPU."); break; default: @@ -724,11 +728,11 @@ void MyFrame::OnShowCPUs(wxCommandEvent& WXUNUSED(event)) void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event) ) { wxMessageDialog dialog(this, - _T("wxWidgets multithreaded application sample\n") - _T("(c) 1998 Julian Smart, Guilhem Lavaux\n") - _T("(c) 1999 Vadim Zeitlin\n") - _T("(c) 2000 Robert Roebling"), - _T("About wxThread sample"), + wxT("wxWidgets multithreaded application sample\n") + wxT("(c) 1998 Julian Smart, Guilhem Lavaux\n") + wxT("(c) 2000 Robert Roebling\n") + wxT("(c) 1999,2009 Vadim Zeitlin"), + wxT("About wxThread sample"), wxOK | wxICON_INFORMATION); dialog.ShowModal(); @@ -756,8 +760,8 @@ void MyFrame::OnStartWorker(wxCommandEvent& WXUNUSED(event)) m_dlgProgress = new wxProgressDialog ( - _T("Progress dialog"), - _T("Wait until the thread terminates or press [Cancel]"), + wxT("Progress dialog"), + wxT("Wait until the thread terminates or press [Cancel]"), 100, this, wxPD_CAN_ABORT | @@ -790,16 +794,285 @@ void MyFrame::OnWorkerEvent(wxThreadEvent& event) { if ( !m_dlgProgress->Update(n) ) { - wxCriticalSectionLocker lock(m_critsectWork); + wxCriticalSectionLocker lock(m_csCancelled); m_cancelled = true; } } } -bool MyFrame::Cancelled() +void MyFrame::OnStartGUIThread(wxCommandEvent& WXUNUSED(event)) { - wxCriticalSectionLocker lock(m_critsectWork); + // we use this to check that disabling logging only affects the main thread + // but the messages from the worker thread will still be logged + wxLogNull noLog; + wxLogMessage("You shouldn't see this message because of wxLogNull"); - return m_cancelled; + MyImageDialog dlg(this); + + dlg.ShowModal(); +} + + +// ---------------------------------------------------------------------------- +// MyImageDialog +// ---------------------------------------------------------------------------- + +BEGIN_EVENT_TABLE(MyImageDialog, wxDialog) + EVT_THREAD(GUITHREAD_EVENT, MyImageDialog::OnGUIThreadEvent) + EVT_PAINT(MyImageDialog::OnPaint) +END_EVENT_TABLE() + +MyImageDialog::MyImageDialog(wxFrame *parent) + : wxDialog(parent, wxID_ANY, "Image created by a secondary thread", + wxDefaultPosition, wxSize(GUITHREAD_BMP_SIZE,GUITHREAD_BMP_SIZE)*1.5, wxDEFAULT_DIALOG_STYLE), + m_thread(this) +{ + m_nCurrentProgress = 0; + + CentreOnScreen(); + + // NOTE: no need to lock m_csBmp until the thread isn't started: + + // create the bitmap + if (!m_bmp.Create(GUITHREAD_BMP_SIZE,GUITHREAD_BMP_SIZE) || !m_bmp.IsOk()) + { + wxLogError("Couldn't create the bitmap!"); + return; + } + + // clean it + wxMemoryDC dc(m_bmp); + dc.SetBackground(*wxBLACK_BRUSH); + dc.Clear(); + + // draw the bitmap from a secondary thread + if ( m_thread.Create() != wxTHREAD_NO_ERROR || + m_thread.Run() != wxTHREAD_NO_ERROR ) + { + wxLogError(wxT("Can't create/run thread!")); + return; + } +} + +MyImageDialog::~MyImageDialog() +{ + // in case our thread is still running and for some reason we are destroyed, + // do wait for the thread to complete as it assumes that its MyImageDialog + // pointer is always valid + m_thread.Delete(); +} + +void MyImageDialog::OnGUIThreadEvent(wxThreadEvent& event) +{ + m_nCurrentProgress = int(((float)event.GetInt()*100)/GUITHREAD_NUM_UPDATES); + + Refresh(); +} + +void MyImageDialog::OnPaint(wxPaintEvent& WXUNUSED(evt)) +{ + wxPaintDC dc(this); + + const wxSize& sz = dc.GetSize(); + + { + // paint the bitmap + wxCriticalSectionLocker locker(m_csBmp); + dc.DrawBitmap(m_bmp, (sz.GetWidth()-GUITHREAD_BMP_SIZE)/2, + (sz.GetHeight()-GUITHREAD_BMP_SIZE)/2); + } + + // paint a sort of progress bar with a 10px border: + dc.SetBrush(*wxRED_BRUSH); + dc.DrawRectangle(10,10, m_nCurrentProgress*(sz.GetWidth()-20)/100,30); + dc.SetTextForeground(*wxBLUE); + dc.DrawText(wxString::Format("%d%%", m_nCurrentProgress), + (sz.GetWidth()-dc.GetCharWidth()*2)/2, + 25-dc.GetCharHeight()/2); +} + +// ---------------------------------------------------------------------------- +// MyThread +// ---------------------------------------------------------------------------- + +MyThread::MyThread() + : wxThread() +{ + m_count = 0; +} + +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(); + } + } +} + +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; +} + + +// ---------------------------------------------------------------------------- +// MyWorkerThread +// ---------------------------------------------------------------------------- + +// define this symbol to 1 to test if the YieldFor() call in the wxProgressDialog::Update +// function provokes a race condition in which the second wxThreadEvent posted by +// MyWorkerThread::Entry is processed by the YieldFor() call of wxProgressDialog::Update +// and results in the destruction of the progress dialog itself, resulting in a crash later. +#define TEST_YIELD_RACE_CONDITION 0 + +MyWorkerThread::MyWorkerThread(MyFrame *frame) + : wxThread() +{ + m_frame = frame; + m_count = 0; +} + +void MyWorkerThread::OnExit() +{ +} + +wxThread::ExitCode MyWorkerThread::Entry() +{ +#if TEST_YIELD_RACE_CONDITION + if ( TestDestroy() ) + return NULL; + + wxThreadEvent event( wxEVT_THREAD, WORKER_EVENT ); + + event.SetInt( 50 ); + wxQueueEvent( m_frame, event.Clone() ); + + event.SetInt(-1); + wxQueueEvent( m_frame, event.Clone() ); +#else + 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, WORKER_EVENT ); + event.SetInt( m_count ); + + // send in a thread-safe way + wxQueueEvent( m_frame, event.Clone() ); + + wxMilliSleep(200); + } + + wxThreadEvent event( wxEVT_THREAD, WORKER_EVENT ); + event.SetInt(-1); // that's all + wxQueueEvent( m_frame, event.Clone() ); +#endif + + return NULL; +} + + +// ---------------------------------------------------------------------------- +// MyGUIThread +// ---------------------------------------------------------------------------- + +wxThread::ExitCode MyGUIThread::Entry() +{ + // uncomment this to check that disabling logging here does disable it for + // this thread -- but not the main one if you also comment out wxLogNull + // line in MyFrame::OnStartGUIThread() + //wxLogNull noLog; + + // this goes to the main window + wxLogMessage("GUI thread starting"); + + // use a thread-specific log target for this thread to show that its + // messages don't appear in the main window while it runs + wxLogBuffer logBuf; + wxLog::SetThreadActiveTarget(&logBuf); + + for (int i=0; i<GUITHREAD_NUM_UPDATES && !TestDestroy(); i++) + { + // inform the GUI toolkit that we're going to use GUI functions + // from a secondary thread: + wxMutexGuiEnter(); + + { + wxCriticalSectionLocker lock(m_dlg->m_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() ); + + if ( !((i + 1) % 10) ) + { + // this message will go to the buffer + wxLogMessage("Step #%d.", i + 1); + } + + // 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); + } + + // now remove the thread-specific thread target + wxLog::SetThreadActiveTarget(NULL); + + // so that this goes to the main window again + wxLogMessage("GUI thread finished."); + + return (ExitCode)0; }