X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/3222fde2afae240176ca9a3edba853e36b885ddb..f1567cddead054974c4a079ed8ee2c798ef998dc:/samples/thread/test.cpp diff --git a/samples/thread/test.cpp b/samples/thread/test.cpp index a2aaaf940c..be40d23a60 100644 --- a/samples/thread/test.cpp +++ b/samples/thread/test.cpp @@ -9,9 +9,14 @@ // Licence: wxWindows license ///////////////////////////////////////////////////////////////////////////// +/* + TODO: use worker threads to update progress controls instead of writing + messages - it will be more visual + */ + #ifdef __GNUG__ -#pragma implementation "test.cpp" -#pragma interface "test.cpp" + #pragma implementation "test.cpp" + #pragma interface "test.cpp" #endif // For compilers that support precompilation, includes "wx/wx.h". @@ -33,48 +38,105 @@ #include "wx/dynarray.h" #include "wx/time.h" +#include "wx/progdlg.h" + +// uncomment this to get some debugging messages from the trace code +//#define TRACE + +class MyThread; +WX_DEFINE_ARRAY(wxThread *, wxArrayThread); + // Define a new application type -class MyApp: public wxApp +class MyApp : public wxApp { public: - bool OnInit(void); + virtual bool OnInit(); + +public: + // all the threads currently alive - as soon as the thread terminates, it's + // removed from the array + wxArrayThread m_threads; + + // crit section protects access to all of the arrays below + wxCriticalSection m_critsect; }; -WX_DEFINE_ARRAY(wxThread *, wxArrayThread); +// Create a new application object +IMPLEMENT_APP(MyApp) // Define a new frame type class MyFrame: public wxFrame { public: // ctor - MyFrame(wxFrame *frame, char *title, int x, int y, int w, int h); + MyFrame(wxFrame *frame, const wxString& title, int x, int y, int w, int h); // operations void WriteText(const wxString& text) { m_txtctrl->WriteText(text); } + // accessors for MyWorkerThread (called in its context!) + bool Cancelled(); + // callbacks void OnQuit(wxCommandEvent& event); void OnAbout(wxCommandEvent& event); + void OnClear(wxCommandEvent& event); void OnStartThread(wxCommandEvent& event); + void OnStartThreads(wxCommandEvent& event); void OnStopThread(wxCommandEvent& event); void OnPauseThread(wxCommandEvent& event); void OnResumeThread(wxCommandEvent& event); - void OnSize(wxSizeEvent &event); - void OnIdle(wxIdleEvent &event); - bool OnClose() { return TRUE; } + void OnStartWorker(wxCommandEvent& event); + void OnWorkerEvent(wxCommandEvent& event); + void OnUpdateWorker(wxUpdateUIEvent& event); -public: - wxArrayThread m_threads; + void OnIdle(wxIdleEvent &event); private: + // helper function - creates a new thread (but doesn't run it) + MyThread *CreateThread(); + + // just some place to put our messages in wxTextCtrl *m_txtctrl; + // remember the number of running threads and total number of threads + size_t m_nRunning, m_nCount; + + // the progress dialog which we show while worker thread is running + wxProgressDialog *m_dlgProgress; + + // was the worker thread cancelled by user? + bool m_cancelled; + + // protects m_cancelled + wxCriticalSection m_critsectWork; + DECLARE_EVENT_TABLE() }; -class MyThread: public wxThread +// 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, + WORKER_EVENT // this one gets sent from the worker thread +}; + +//-------------------------------------------------- +// GUI thread +//-------------------------------------------------- + +class MyThread : public wxThread { public: MyThread(MyFrame *frame); @@ -82,6 +144,10 @@ public: // thread execution starts here virtual void *Entry(); + // called when the thread exits - whether it terminates normally or is + // stopped with Delete() (but not when it is Kill()ed!) + virtual void OnExit(); + // write something to the text control void WriteText(const wxString& text); @@ -100,90 +166,172 @@ MyThread::MyThread(MyFrame *frame) void MyThread::WriteText(const wxString& text) { wxString msg; - msg << wxTime().FormatTime() << ": " << text; // before doing any GUI calls we must ensure that this thread is the only // one doing it! + wxMutexGuiEnter(); + + msg << text; m_frame->WriteText(msg); + wxMutexGuiLeave(); } +void MyThread::OnExit() +{ + wxCriticalSectionLocker locker(wxGetApp().m_critsect); + + wxGetApp().m_threads.Remove(this); +} + void *MyThread::Entry() { wxString text; - DeferDestroy(TRUE); - - text.Printf("Thread 0x%x started.\n", GetID()); + text.Printf("Thread 0x%x started (priority = %d).\n", + GetId(), GetPriority()); WriteText(text); + // wxLogMessage(text); -- test wxLog thread safeness - for ( m_count = 0; m_count < 20; m_count++ ) + for ( m_count = 0; m_count < 10; m_count++ ) { // check if we were asked to exit if ( TestDestroy() ) break; - text.Printf("[%u] Thread 0x%x here.\n", m_count, GetID()); + text.Printf("[%u] Thread 0x%x here.\n", m_count, GetId()); WriteText(text); - wxSleep(1); + // wxSleep() can't be called from non-GUI thread! + wxThread::Sleep(1000); } - text.Printf("Thread 0x%x finished.\n", GetID()); + text.Printf("Thread 0x%x finished.\n", GetId()); WriteText(text); + // wxLogMessage(text); -- test wxLog thread safeness return NULL; } -// ID for the menu commands -enum +//-------------------------------------------------- +// worker thread +//-------------------------------------------------- + +class MyWorkerThread : public wxThread { - TEST_QUIT = 1, - TEST_TEXT = 101, - TEST_ABOUT = 102, - TEST_START_THREAD = 103, - TEST_STOP_THREAD = 104, - TEST_PAUSE_THREAD = 105, - TEST_RESUME_THREAD = 106 +public: + MyWorkerThread(MyFrame *frame); + + // thread execution starts here + virtual void *Entry(); + + // called when the thread exits - whether it terminates normally or is + // stopped with Delete() (but not when it is Kill()ed!) + virtual void OnExit(); + +public: + MyFrame *m_frame; + size_t m_count; }; +MyWorkerThread::MyWorkerThread(MyFrame *frame) + : wxThread() +{ + m_frame = frame; + m_count = 0; +} + +void MyWorkerThread::OnExit() +{ +} + +void *MyWorkerThread::Entry() +{ + for ( m_count = 0; !m_frame->Cancelled() && (m_count < 100); m_count++ ) + { + // check if we were asked to exit + 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); + } + + wxCommandEvent event( wxEVT_COMMAND_MENU_SELECTED, WORKER_EVENT ); + event.SetInt(-1); // that's all + wxPostEvent( m_frame, event ); + + return NULL; +} + +//-------------------------------------------------- +// main program +//-------------------------------------------------- + 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_SIZE(MyFrame::OnSize) + EVT_UPDATE_UI(TEST_START_WORKER, MyFrame::OnUpdateWorker) + EVT_MENU(TEST_START_WORKER, MyFrame::OnStartWorker) + EVT_MENU(WORKER_EVENT, MyFrame::OnWorkerEvent) EVT_IDLE(MyFrame::OnIdle) END_EVENT_TABLE() -// Create a new application object -IMPLEMENT_APP (MyApp) - // `Main program' equivalent, creating windows and returning main app frame bool MyApp::OnInit() { +#ifdef TRACE + wxLog::AddTraceMask("thread"); +#endif + // Create the main frame window - MyFrame *frame = new MyFrame((wxFrame *)NULL, "", 50, 50, 450, 340); + MyFrame *frame = new MyFrame((wxFrame *)NULL, "wxWindows 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->Append(TEST_QUIT, "E&xit"); + 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"); - thread_menu->Append(TEST_STOP_THREAD, "S&top a running thread"); + 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_PAUSE_THREAD, "&Pause a running thread"); - thread_menu->Append(TEST_RESUME_THREAD, "&Resume suspended thread"); + thread_menu->Append(TEST_START_WORKER, "Start &worker thread\tCtrl-W"); + menu_bar->Append(thread_menu, "&Thread"); frame->SetMenuBar(menu_bar); @@ -196,106 +344,283 @@ bool MyApp::OnInit() } // My frame constructor -MyFrame::MyFrame(wxFrame *frame, char *title, int x, int y, int w, int h) +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)) { - wxPanel *panel = new wxPanel(this, -1, wxPoint(0, 0), wxSize(400, 200), - wxTAB_TRAVERSAL); + m_nRunning = m_nCount = 0; + + m_dlgProgress = (wxProgressDialog *)NULL; + + CreateStatusBar(2); - m_txtctrl = new wxTextCtrl(panel, -1, "", wxPoint(10,30), wxSize(390, 190), - wxTE_MULTILINE); + m_txtctrl = new wxTextCtrl(this, -1, "", wxPoint(0, 0), wxSize(0, 0), + wxTE_MULTILINE | wxTE_READONLY); - (void)new wxStaticText(panel, -1, "Log window", wxPoint(10,10)); } -void MyFrame::OnStartThread(wxCommandEvent& WXUNUSED(event) ) +MyThread *MyFrame::CreateThread() { MyThread *thread = new MyThread(this); - thread->Create(); + if ( thread->Create() != wxTHREAD_NO_ERROR ) + { + wxLogError("Can't create thread!"); + } + + wxCriticalSectionLocker enter(wxGetApp().m_critsect); + wxGetApp().m_threads.Add(thread); - m_threads.Add(thread); + return thread; } -void MyFrame::OnStopThread(wxCommandEvent& WXUNUSED(event) ) +void MyFrame::OnStartThreads(wxCommandEvent& WXUNUSED(event) ) { - int no_thrd = m_threads.Count()-1; + static long s_num = 10; + + s_num = wxGetNumberFromUser("How many threads to start: ", "", + "wxThread sample", s_num, 1, 10000, this); + if ( s_num == -1 ) + { + s_num = 10; - if (no_thrd < 0) return; + } + + size_t count = (size_t)s_num, n; + + wxArrayThread threads; + + // first create them all... + for ( n = 0; n < count; n++ ) + { + wxThread *thr = CreateThread(); + + // we want to show the effect of SetPriority(): the first thread will + // have the lowest priority, the second - the highest, all the rest + // the normal one + if ( n == 0 ) + thr->SetPriority(WXTHREAD_MIN_PRIORITY); + else if ( n == 1 ) + thr->SetPriority(WXTHREAD_MAX_PRIORITY); + else + thr->SetPriority(WXTHREAD_DEFAULT_PRIORITY); + + threads.Add(thr); + } + + wxString msg; + msg.Printf("%d new threads created.", count); + SetStatusText(msg, 1); + + // ...and then start them + for ( n = 0; n < count; n++ ) + { + threads[n]->Run(); + } +} + +void MyFrame::OnStartThread(wxCommandEvent& WXUNUSED(event) ) +{ + MyThread *thread = CreateThread(); + + if ( thread->Run() != wxTHREAD_NO_ERROR ) + { + wxLogError("Can't start thread!"); + } + + SetStatusText("New thread started.", 1); +} + +void MyFrame::OnStopThread(wxCommandEvent& WXUNUSED(event) ) +{ + wxGetApp().m_critsect.Enter(); + + // stop the last thread + if ( wxGetApp().m_threads.IsEmpty() ) + { + wxLogError("No thread to stop!"); + + wxGetApp().m_critsect.Leave(); + } + else + { + wxThread *thread = wxGetApp().m_threads.Last(); + + // it's important to leave critical section before calling Delete() + // because delete will (implicitly) call OnExit() which also tries + // to enter the same crit section - would dead lock. + wxGetApp().m_critsect.Leave(); + + thread->Delete(); - delete m_threads[no_thrd]; - m_threads.Remove(no_thrd); + SetStatusText("Thread stopped.", 1); + } } void MyFrame::OnResumeThread(wxCommandEvent& WXUNUSED(event) ) { + wxCriticalSectionLocker enter(wxGetApp().m_critsect); + // resume first suspended thread - size_t n = 0; - while ( n < m_threads.Count() && m_threads[n]->IsPaused() ) - n--; + size_t n = 0, count = wxGetApp().m_threads.Count(); + while ( n < count && !wxGetApp().m_threads[n]->IsPaused() ) + n++; - if ( n < 0 ) - wxLogError("No thread to pause!"); + if ( n == count ) + { + wxLogError("No thread to resume!"); + } else - m_threads[n]->Resume(); + { + wxGetApp().m_threads[n]->Resume(); + + SetStatusText("Thread resumed.", 1); + } } void MyFrame::OnPauseThread(wxCommandEvent& WXUNUSED(event) ) { + wxCriticalSectionLocker enter(wxGetApp().m_critsect); + // pause last running thread - int n = m_threads.Count() - 1; - while ( n >= 0 && !m_threads[n]->IsRunning() ) + int n = wxGetApp().m_threads.Count() - 1; + while ( n >= 0 && !wxGetApp().m_threads[n]->IsRunning() ) n--; if ( n < 0 ) + { wxLogError("No thread to pause!"); + } else - m_threads[n]->Pause(); + { + wxGetApp().m_threads[n]->Pause(); + + SetStatusText("Thread paused.", 1); + } } // set the frame title indicating the current number of threads void MyFrame::OnIdle(wxIdleEvent &event) { + // update the counts of running/total threads size_t nRunning = 0, - nCount = m_threads.Count(); + nCount = wxGetApp().m_threads.Count(); for ( size_t n = 0; n < nCount; n++ ) { - if ( m_threads[n]->IsRunning() ) + if ( wxGetApp().m_threads[n]->IsRunning() ) nRunning++; } - wxString title; - title.Printf("wxWindows thread sample (%u threads, %u running).", - nCount, nRunning); - SetTitle(title); -} - -void MyFrame::OnSize(wxSizeEvent& event) -{ - wxFrame::OnSize(event); - - wxSize size( GetClientSize() ); + if ( nCount != m_nCount || nRunning != m_nRunning ) + { + m_nRunning = nRunning; + m_nCount = nCount; - m_txtctrl->SetSize( 10, 30, size.x-20, size.y-40 ); + wxLogStatus(this, "%u threads total, %u running.", nCount, nRunning); + } + //else: avoid flicker - don't print anything } void MyFrame::OnQuit(wxCommandEvent& WXUNUSED(event) ) { - for ( size_t i = 0; i < m_threads.Count(); i++ ) - delete m_threads[i]; + size_t count = wxGetApp().m_threads.Count(); + for ( size_t i = 0; i < count; i++ ) + { + wxGetApp().m_threads[0]->Delete(); + } Close(TRUE); } void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event) ) { - wxMessageDialog dialog(this, "wxThread sample (based on minimal)\n" - "Julian Smart and Guilhem Lavaux", + 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", wxOK | wxICON_INFORMATION); dialog.ShowModal(); } +void MyFrame::OnClear(wxCommandEvent& WXUNUSED(event)) +{ +#ifdef TRACE + // log a separator + wxLogTrace("-------- log window cleared --------"); +#endif + + m_txtctrl->Clear(); +} + +void MyFrame::OnUpdateWorker(wxUpdateUIEvent& event) +{ + event.Enable( m_dlgProgress == NULL ); +} + +void MyFrame::OnStartWorker(wxCommandEvent& WXUNUSED(event)) +{ + MyWorkerThread *thread = new MyWorkerThread(this); + + if ( thread->Create() != wxTHREAD_NO_ERROR ) + { + wxLogError("Can't create thread!"); + } + + m_dlgProgress = new wxProgressDialog + ( + "Progress dialog", + "Wait until the thread terminates or press [Cancel]", + 100, + this, + wxPD_CAN_ABORT | + wxPD_APP_MODAL | + wxPD_ELAPSED_TIME | + wxPD_ESTIMATED_TIME | + wxPD_REMAINING_TIME + ); + + // thread is not running yet, no need for crit sect + m_cancelled = FALSE; + + thread->Run(); +} + +void MyFrame::OnWorkerEvent(wxCommandEvent& event) +{ +#if 0 + WriteText( "Got message from worker thread: " ); + WriteText( event.GetString() ); + WriteText( "\n" ); +#else + 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_critsectWork); + + m_cancelled = TRUE; + } + } +#endif +} + +bool MyFrame::Cancelled() +{ + wxCriticalSectionLocker lock(m_critsectWork); + return m_cancelled; +}