X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/f6bcfd974ef26faf6f91a62cac09827e09463fd1..004f4002877acc09d8bc213602f55b22bdbae345:/samples/thread/thread.cpp diff --git a/samples/thread/thread.cpp b/samples/thread/thread.cpp index 19a99c2d6c..248b3dee44 100644 --- a/samples/thread/thread.cpp +++ b/samples/thread/thread.cpp @@ -1,19 +1,14 @@ ///////////////////////////////////////////////////////////////////////////// // Name: thread.cpp -// Purpose: wxWindows thread sample -// Author: Julian Smart(minimal)/Guilhem Lavaux(thread test) +// Purpose: wxWidgets thread sample +// Author: Guilhem Lavaux, Vadim Zeitlin // Modified by: // Created: 06/16/98 // RCS-ID: $Id$ -// Copyright: (c) Julian Smart, Markus Holzem, Guilhem Lavaux +// Copyright: (c) 1998-2002 wxWidgets team // Licence: wxWindows license ///////////////////////////////////////////////////////////////////////////// -/* - TODO: use worker threads to update progress controls instead of writing - messages - it will be more visual - */ - // For compilers that support precompilation, includes "wx/wx.h". #include "wx/wxprec.h" @@ -31,20 +26,31 @@ #include "wx/thread.h" #include "wx/dynarray.h" -#include "wx/time.h" +#include "wx/numdlg.h" #include "wx/progdlg.h" -// uncomment this to get some debugging messages from the trace code -//#define TRACE +#include "../sample.xpm" + +// define this to use wxExecute in the exec tests, otherwise just use system +#define USE_EXECUTE + +#ifdef USE_EXECUTE + #define EXEC(cmd) wxExecute((cmd), wxEXEC_SYNC) +#else + #define EXEC(cmd) system(cmd) +#endif class MyThread; -WX_DEFINE_ARRAY(wxThread *, wxArrayThread); +WX_DEFINE_ARRAY_PTR(wxThread *, wxArrayThread); // Define a new application type class MyApp : public wxApp { public: + MyApp(); + virtual ~MyApp(){}; + virtual bool OnInit(); public: @@ -54,6 +60,13 @@ public: // crit section protects access to all of the arrays below wxCriticalSection m_critsect; + + // semaphore used to wait for the threads to exit, see MyFrame::OnQuit() + wxSemaphore m_semAllDone; + + // the last exiting thread should post to m_semAllDone if this is true + // (protected by the same m_critsect) + bool m_waitingUntilAllDone; }; // Create a new application object @@ -65,6 +78,7 @@ class MyFrame: public wxFrame public: // ctor MyFrame(wxFrame *frame, const wxString& title, int x, int y, int w, int h); + virtual ~MyFrame(); // operations void WriteText(const wxString& text) { m_txtctrl->WriteText(text); } @@ -72,9 +86,9 @@ public: // accessors for MyWorkerThread (called in its context!) bool Cancelled(); +protected: // callbacks void OnQuit(wxCommandEvent& event); - void OnAbout(wxCommandEvent& event); void OnClear(wxCommandEvent& event); void OnStartThread(wxCommandEvent& event); @@ -87,6 +101,12 @@ public: void OnWorkerEvent(wxCommandEvent& event); void OnUpdateWorker(wxUpdateUIEvent& event); + void OnExecMain(wxCommandEvent& event); + void OnExecThread(wxCommandEvent& event); + + void OnShowCPUs(wxCommandEvent& event); + void OnAbout(wxCommandEvent& event); + void OnIdle(wxIdleEvent &event); private: @@ -114,22 +134,28 @@ private: // ID for the menu commands enum { - TEST_QUIT = 1, - TEST_TEXT = 101, - TEST_ABOUT, - TEST_CLEAR, - TEST_START_THREAD = 201, - TEST_START_THREADS, - TEST_STOP_THREAD, - TEST_PAUSE_THREAD, - TEST_RESUME_THREAD, - TEST_START_WORKER, + THREAD_QUIT = wxID_EXIT, + THREAD_ABOUT = wxID_ABOUT, + THREAD_TEXT = 101, + THREAD_CLEAR, + THREAD_START_THREAD = 201, + THREAD_START_THREADS, + THREAD_STOP_THREAD, + THREAD_PAUSE_THREAD, + THREAD_RESUME_THREAD, + THREAD_START_WORKER, + + THREAD_EXEC_MAIN, + THREAD_EXEC_THREAD, + + THREAD_SHOWCPUS, + WORKER_EVENT // this one gets sent from the worker thread }; -//-------------------------------------------------- +// ---------------------------------------------------------------------------- // GUI thread -//-------------------------------------------------- +// ---------------------------------------------------------------------------- class MyThread : public wxThread { @@ -177,14 +203,27 @@ void MyThread::OnExit() { wxCriticalSectionLocker locker(wxGetApp().m_critsect); - wxGetApp().m_threads.Remove(this); + 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_waitingUntilAllDone ) + { + wxGetApp().m_waitingUntilAllDone = false; + + wxGetApp().m_semAllDone.Post(); + } + } } void *MyThread::Entry() { wxString text; - text.Printf("Thread 0x%x started (priority = %d).\n", + text.Printf(wxT("Thread 0x%lx started (priority = %u).\n"), GetId(), GetPriority()); WriteText(text); // wxLogMessage(text); -- test wxLog thread safeness @@ -195,23 +234,23 @@ void *MyThread::Entry() if ( TestDestroy() ) break; - text.Printf("[%u] Thread 0x%x here.\n", m_count, GetId()); + 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("Thread 0x%x finished.\n", GetId()); + text.Printf(wxT("Thread 0x%lx finished.\n"), GetId()); WriteText(text); // wxLogMessage(text); -- test wxLog thread safeness return NULL; } -//-------------------------------------------------- +// ---------------------------------------------------------------------------- // worker thread -//-------------------------------------------------- +// ---------------------------------------------------------------------------- class MyWorkerThread : public wxThread { @@ -249,20 +288,13 @@ void *MyWorkerThread::Entry() if ( TestDestroy() ) break; - wxString text; - text.Printf("[%u] Thread 0x%x here!!", m_count, GetId()); - // create any type of command event here wxCommandEvent event( wxEVT_COMMAND_MENU_SELECTED, WORKER_EVENT ); event.SetInt( m_count ); - event.SetString( text ); // send in a thread-safe way wxPostEvent( m_frame, event ); - // same as: - // m_frame->AddPendingEvent( event ); - // wxSleep() can't be called from non-main thread! wxThread::Sleep(200); } @@ -274,93 +306,184 @@ void *MyWorkerThread::Entry() return NULL; } -//-------------------------------------------------- -// main program -//-------------------------------------------------- +// ---------------------------------------------------------------------------- +// a thread which simply calls wxExecute +// ---------------------------------------------------------------------------- + +class MyExecThread : public wxThread +{ +public: + MyExecThread(const wxChar *command) : wxThread(wxTHREAD_JOINABLE), + m_command(command) + { + Create(); + } + + virtual ExitCode Entry() + { + return (ExitCode)EXEC(m_command); + } + +private: + wxString m_command; +}; + +// ---------------------------------------------------------------------------- +// implementation +// ---------------------------------------------------------------------------- BEGIN_EVENT_TABLE(MyFrame, wxFrame) - EVT_MENU(TEST_QUIT, MyFrame::OnQuit) - EVT_MENU(TEST_ABOUT, MyFrame::OnAbout) - EVT_MENU(TEST_CLEAR, MyFrame::OnClear) - EVT_MENU(TEST_START_THREAD, MyFrame::OnStartThread) - EVT_MENU(TEST_START_THREADS, MyFrame::OnStartThreads) - EVT_MENU(TEST_STOP_THREAD, MyFrame::OnStopThread) - EVT_MENU(TEST_PAUSE_THREAD, MyFrame::OnPauseThread) - EVT_MENU(TEST_RESUME_THREAD, MyFrame::OnResumeThread) - - EVT_UPDATE_UI(TEST_START_WORKER, MyFrame::OnUpdateWorker) - EVT_MENU(TEST_START_WORKER, MyFrame::OnStartWorker) + EVT_MENU(THREAD_QUIT, MyFrame::OnQuit) + EVT_MENU(THREAD_CLEAR, MyFrame::OnClear) + EVT_MENU(THREAD_START_THREAD, MyFrame::OnStartThread) + EVT_MENU(THREAD_START_THREADS, MyFrame::OnStartThreads) + EVT_MENU(THREAD_STOP_THREAD, MyFrame::OnStopThread) + EVT_MENU(THREAD_PAUSE_THREAD, MyFrame::OnPauseThread) + EVT_MENU(THREAD_RESUME_THREAD, MyFrame::OnResumeThread) + + EVT_MENU(THREAD_EXEC_MAIN, MyFrame::OnExecMain) + EVT_MENU(THREAD_EXEC_THREAD, MyFrame::OnExecThread) + + 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_MENU(WORKER_EVENT, MyFrame::OnWorkerEvent) EVT_IDLE(MyFrame::OnIdle) END_EVENT_TABLE() +MyApp::MyApp() + : m_semAllDone() +{ + m_waitingUntilAllDone = false; +} + // `Main program' equivalent, creating windows and returning main app frame bool MyApp::OnInit() { -#ifdef TRACE - wxLog::AddTraceMask("thread"); -#endif + // 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, "wxWindows threads sample", + MyFrame *frame = new MyFrame((wxFrame *)NULL, _T("wxWidgets threads sample"), 50, 50, 450, 340); // Make a menubar - wxMenu *file_menu = new wxMenu; - - file_menu->Append(TEST_CLEAR, "&Clear log\tCtrl-L"); - file_menu->AppendSeparator(); - file_menu->Append(TEST_ABOUT, "&About"); - file_menu->AppendSeparator(); - file_menu->Append(TEST_QUIT, "E&xit\tAlt-X"); - wxMenuBar *menu_bar = new wxMenuBar; - menu_bar->Append(file_menu, "&File"); - - wxMenu *thread_menu = new wxMenu; - thread_menu->Append(TEST_START_THREAD, "&Start a new thread\tCtrl-N"); - thread_menu->Append(TEST_START_THREADS, "Start &many threads at once"); - thread_menu->Append(TEST_STOP_THREAD, "S&top a running thread\tCtrl-S"); - thread_menu->AppendSeparator(); - thread_menu->Append(TEST_PAUSE_THREAD, "&Pause a running thread\tCtrl-P"); - thread_menu->Append(TEST_RESUME_THREAD, "&Resume suspended thread\tCtrl-R"); - thread_menu->AppendSeparator(); - thread_menu->Append(TEST_START_WORKER, "Start &worker thread\tCtrl-W"); - - menu_bar->Append(thread_menu, "&Thread"); - frame->SetMenuBar(menu_bar); - + wxMenuBar *menuBar = new wxMenuBar; + + wxMenu *menuFile = new wxMenu; + menuFile->Append(THREAD_CLEAR, _T("&Clear log\tCtrl-L")); + menuFile->AppendSeparator(); + menuFile->Append(THREAD_QUIT, _T("E&xit\tAlt-X")); + menuBar->Append(menuFile, _T("&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->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->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")); + + wxMenu *menuHelp = new wxMenu; + menuHelp->Append(THREAD_SHOWCPUS, _T("&Show CPU count")); + menuHelp->AppendSeparator(); + menuHelp->Append(THREAD_ABOUT, _T("&About...")); + menuBar->Append(menuHelp, _T("&Help")); + + frame->SetMenuBar(menuBar); + // Show the frame - frame->Show(TRUE); + frame->Show(true); SetTopWindow(frame); - return TRUE; + return true; } // My frame constructor MyFrame::MyFrame(wxFrame *frame, const wxString& title, int x, int y, int w, int h) - : wxFrame(frame, -1, title, wxPoint(x, y), wxSize(w, h)) + : wxFrame(frame, wxID_ANY, title, wxPoint(x, y), wxSize(w, h)) { + SetIcon(wxIcon(sample_xpm)); + m_nRunning = m_nCount = 0; m_dlgProgress = (wxProgressDialog *)NULL; +#if wxUSE_STATUSBAR CreateStatusBar(2); +#endif // wxUSE_STATUSBAR - m_txtctrl = new wxTextCtrl(this, -1, "", wxPoint(0, 0), wxSize(0, 0), + m_txtctrl = new wxTextCtrl(this, wxID_ANY, _T(""), wxPoint(0, 0), wxSize(0, 0), wxTE_MULTILINE | wxTE_READONLY); } +MyFrame::~MyFrame() +{ + // 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 + wxThread *thread; + + wxGetApp().m_critsect.Enter(); + + // check if we have any threads running first + const wxArrayThread& threads = wxGetApp().m_threads; + size_t count = threads.GetCount(); + + if ( count ) + { + // set the flag for MyThread::OnExit() + wxGetApp().m_waitingUntilAllDone = true; + + // stop all threads + while ( ! threads.IsEmpty() ) + { + thread = threads.Last(); + + wxGetApp().m_critsect.Leave(); + + thread->Delete(); + + wxGetApp().m_critsect.Enter(); + } + } + + wxGetApp().m_critsect.Leave(); + + if ( count ) + { + // now wait for them to really terminate + wxGetApp().m_semAllDone.Wait(); + } + //else: no threads to terminate, no condition to wait for +} + MyThread *MyFrame::CreateThread() { MyThread *thread = new MyThread(this); if ( thread->Create() != wxTHREAD_NO_ERROR ) { - wxLogError("Can't create thread!"); + wxLogError(wxT("Can't create thread!")); } wxCriticalSectionLocker enter(wxGetApp().m_critsect); @@ -373,8 +496,8 @@ void MyFrame::OnStartThreads(wxCommandEvent& WXUNUSED(event) ) { static long s_num = 10; - s_num = wxGetNumberFromUser("How many threads to start: ", "", - "wxThread sample", s_num, 1, 10000, this); + s_num = wxGetNumberFromUser(_T("How many threads to start: "), _T(""), + _T("wxThread sample"), s_num, 1, 10000, this); if ( s_num == -1 ) { s_num = 10; @@ -404,9 +527,11 @@ void MyFrame::OnStartThreads(wxCommandEvent& WXUNUSED(event) ) threads.Add(thr); } +#if wxUSE_STATUSBAR wxString msg; - msg.Printf("%d new threads created.", count); + msg.Printf(wxT("%d new threads created."), count); SetStatusText(msg, 1); +#endif // wxUSE_STATUSBAR // ...and then start them for ( n = 0; n < count; n++ ) @@ -421,10 +546,12 @@ void MyFrame::OnStartThread(wxCommandEvent& WXUNUSED(event) ) if ( thread->Run() != wxTHREAD_NO_ERROR ) { - wxLogError("Can't start thread!"); + wxLogError(wxT("Can't start thread!")); } - SetStatusText("New thread started.", 1); +#if wxUSE_STATUSBAR + SetStatusText(_T("New thread started."), 1); +#endif // wxUSE_STATUSBAR } void MyFrame::OnStopThread(wxCommandEvent& WXUNUSED(event) ) @@ -434,7 +561,7 @@ void MyFrame::OnStopThread(wxCommandEvent& WXUNUSED(event) ) // stop the last thread if ( wxGetApp().m_threads.IsEmpty() ) { - wxLogError("No thread to stop!"); + wxLogError(wxT("No thread to stop!")); wxGetApp().m_critsect.Leave(); } @@ -449,7 +576,9 @@ void MyFrame::OnStopThread(wxCommandEvent& WXUNUSED(event) ) thread->Delete(); - SetStatusText("Thread stopped.", 1); +#if wxUSE_STATUSBAR + SetStatusText(_T("Thread stopped."), 1); +#endif // wxUSE_STATUSBAR } } @@ -464,13 +593,15 @@ void MyFrame::OnResumeThread(wxCommandEvent& WXUNUSED(event) ) if ( n == count ) { - wxLogError("No thread to resume!"); + wxLogError(wxT("No thread to resume!")); } else { wxGetApp().m_threads[n]->Resume(); - SetStatusText("Thread resumed.", 1); +#if wxUSE_STATUSBAR + SetStatusText(_T("Thread resumed."), 1); +#endif // wxUSE_STATUSBAR } } @@ -485,19 +616,23 @@ void MyFrame::OnPauseThread(wxCommandEvent& WXUNUSED(event) ) if ( n < 0 ) { - wxLogError("No thread to pause!"); + wxLogError(wxT("No thread to pause!")); } else { wxGetApp().m_threads[n]->Pause(); - SetStatusText("Thread paused.", 1); +#if wxUSE_STATUSBAR + SetStatusText(_T("Thread paused."), 1); +#endif // wxUSE_STATUSBAR } } // set the frame title indicating the current number of threads -void MyFrame::OnIdle(wxIdleEvent &event) +void MyFrame::OnIdle(wxIdleEvent& event) { + wxCriticalSectionLocker enter(wxGetApp().m_critsect); + // update the counts of running/total threads size_t nRunning = 0, nCount = wxGetApp().m_threads.Count(); @@ -512,29 +647,67 @@ void MyFrame::OnIdle(wxIdleEvent &event) m_nRunning = nRunning; m_nCount = nCount; - wxLogStatus(this, "%u threads total, %u running.", nCount, nRunning); + wxLogStatus(this, wxT("%u threads total, %u running."), nCount, nRunning); } //else: avoid flicker - don't print anything + + event.Skip(); } void MyFrame::OnQuit(wxCommandEvent& WXUNUSED(event) ) { - size_t count = wxGetApp().m_threads.Count(); - for ( size_t i = 0; i < count; i++ ) + Close(true); +} + +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(); + + wxLogMessage(wxT("The exit code from a child thread is %ld"), + (long)thread.Wait()); +} + +void MyFrame::OnShowCPUs(wxCommandEvent& WXUNUSED(event)) +{ + wxString msg; + + int nCPUs = wxThread::GetCPUCount(); + switch ( nCPUs ) { - wxGetApp().m_threads[0]->Delete(); + case -1: + msg = _T("Unknown number of CPUs"); + break; + + case 0: + msg = _T("WARNING: you're running without any CPUs!"); + break; + + case 1: + msg = _T("This system only has one CPU."); + break; + + default: + msg.Printf(wxT("This system has %d CPUs"), nCPUs); } - Close(TRUE); + wxLogMessage(msg); } void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event) ) { - wxMessageDialog dialog(this, "wxWindows multithreaded application sample\n" - "(c) 1998 Julian Smart, Guilhem Lavaux\n" - "(c) 1999 Vadim Zeitlin\n" - "(c) 2000 Robert Roebling", - "About wxThread sample", + 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"), wxOK | wxICON_INFORMATION); dialog.ShowModal(); @@ -542,11 +715,6 @@ void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event) ) void MyFrame::OnClear(wxCommandEvent& WXUNUSED(event)) { -#ifdef TRACE - // log a separator - wxLogTrace("-------- log window cleared --------"); -#endif - m_txtctrl->Clear(); } @@ -561,13 +729,14 @@ void MyFrame::OnStartWorker(wxCommandEvent& WXUNUSED(event)) if ( thread->Create() != wxTHREAD_NO_ERROR ) { - wxLogError("Can't create thread!"); + wxLogError(wxT("Can't create thread!")); + return; } m_dlgProgress = new wxProgressDialog ( - "Progress dialog", - "Wait until the thread terminates or press [Cancel]", + _T("Progress dialog"), + _T("Wait until the thread terminates or press [Cancel]"), 100, this, wxPD_CAN_ABORT | @@ -578,7 +747,7 @@ void MyFrame::OnStartWorker(wxCommandEvent& WXUNUSED(event)) ); // thread is not running yet, no need for crit sect - m_cancelled = FALSE; + m_cancelled = false; thread->Run(); } @@ -586,9 +755,9 @@ void MyFrame::OnStartWorker(wxCommandEvent& WXUNUSED(event)) void MyFrame::OnWorkerEvent(wxCommandEvent& event) { #if 0 - WriteText( "Got message from worker thread: " ); + WriteText( _T("Got message from worker thread: ") ); WriteText( event.GetString() ); - WriteText( "\n" ); + WriteText( _T("\n") ); #else int n = event.GetInt(); if ( n == -1 ) @@ -607,7 +776,7 @@ void MyFrame::OnWorkerEvent(wxCommandEvent& event) { wxCriticalSectionLocker lock(m_critsectWork); - m_cancelled = TRUE; + m_cancelled = true; } } #endif