]> git.saurik.com Git - wxWidgets.git/blob - samples/thread/thread.cpp
always let the multiline text control process the Enter key presses
[wxWidgets.git] / samples / thread / thread.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: thread.cpp
3 // Purpose: wxWindows thread sample
4 // Author: Julian Smart(minimal)/Guilhem Lavaux(thread test)
5 // Modified by:
6 // Created: 06/16/98
7 // RCS-ID: $Id$
8 // Copyright: (c) Julian Smart, Markus Holzem, Guilhem Lavaux
9 // Licence: wxWindows license
10 /////////////////////////////////////////////////////////////////////////////
11
12 /*
13 TODO: use worker threads to update progress controls instead of writing
14 messages - it will be more visual
15 */
16
17 // For compilers that support precompilation, includes "wx/wx.h".
18 #include "wx/wxprec.h"
19
20 #ifdef __BORLANDC__
21 #pragma hdrstop
22 #endif
23
24 #ifndef WX_PRECOMP
25 #include "wx/wx.h"
26 #endif
27
28 #if !wxUSE_THREADS
29 #error "This sample requires thread support!"
30 #endif // wxUSE_THREADS
31
32 #include "wx/thread.h"
33 #include "wx/dynarray.h"
34 #include "wx/time.h"
35
36 #include "wx/progdlg.h"
37
38 #define USE_EXECUTE
39 #ifdef USE_EXECUTE
40 #define EXEC(cmd) wxExecute((cmd), wxEXEC_SYNC)
41 #else
42 #define EXEC(cmd) system(cmd)
43 #endif
44
45 class MyThread;
46 WX_DEFINE_ARRAY(wxThread *, wxArrayThread);
47
48 // Define a new application type
49 class MyApp : public wxApp
50 {
51 public:
52 virtual bool OnInit();
53
54 public:
55 // all the threads currently alive - as soon as the thread terminates, it's
56 // removed from the array
57 wxArrayThread m_threads;
58
59 // crit section protects access to all of the arrays below
60 wxCriticalSection m_critsect;
61 };
62
63 // Create a new application object
64 IMPLEMENT_APP(MyApp)
65
66 // Define a new frame type
67 class MyFrame: public wxFrame
68 {
69 public:
70 // ctor
71 MyFrame(wxFrame *frame, const wxString& title, int x, int y, int w, int h);
72
73 // operations
74 void WriteText(const wxString& text) { m_txtctrl->WriteText(text); }
75
76 // accessors for MyWorkerThread (called in its context!)
77 bool Cancelled();
78
79 // callbacks
80 void OnQuit(wxCommandEvent& event);
81 void OnClear(wxCommandEvent& event);
82
83 void OnStartThread(wxCommandEvent& event);
84 void OnStartThreads(wxCommandEvent& event);
85 void OnStopThread(wxCommandEvent& event);
86 void OnPauseThread(wxCommandEvent& event);
87 void OnResumeThread(wxCommandEvent& event);
88
89 void OnStartWorker(wxCommandEvent& event);
90 void OnWorkerEvent(wxCommandEvent& event);
91 void OnUpdateWorker(wxUpdateUIEvent& event);
92
93 void OnExecMain(wxCommandEvent& event);
94 void OnExecThread(wxCommandEvent& event);
95
96 void OnShowCPUs(wxCommandEvent& event);
97 void OnAbout(wxCommandEvent& event);
98
99 void OnIdle(wxIdleEvent &event);
100
101 private:
102 // helper function - creates a new thread (but doesn't run it)
103 MyThread *CreateThread();
104
105 // just some place to put our messages in
106 wxTextCtrl *m_txtctrl;
107
108 // remember the number of running threads and total number of threads
109 size_t m_nRunning, m_nCount;
110
111 // the progress dialog which we show while worker thread is running
112 wxProgressDialog *m_dlgProgress;
113
114 // was the worker thread cancelled by user?
115 bool m_cancelled;
116
117 // protects m_cancelled
118 wxCriticalSection m_critsectWork;
119
120 DECLARE_EVENT_TABLE()
121 };
122
123 // ID for the menu commands
124 enum
125 {
126 THREAD_QUIT = 1,
127 THREAD_TEXT = 101,
128 THREAD_CLEAR,
129 THREAD_START_THREAD = 201,
130 THREAD_START_THREADS,
131 THREAD_STOP_THREAD,
132 THREAD_PAUSE_THREAD,
133 THREAD_RESUME_THREAD,
134 THREAD_START_WORKER,
135
136 THREAD_EXEC_MAIN,
137 THREAD_EXEC_THREAD,
138
139 THREAD_SHOWCPUS,
140 THREAD_ABOUT,
141
142 WORKER_EVENT // this one gets sent from the worker thread
143 };
144
145 // ----------------------------------------------------------------------------
146 // GUI thread
147 // ----------------------------------------------------------------------------
148
149 class MyThread : public wxThread
150 {
151 public:
152 MyThread(MyFrame *frame);
153
154 // thread execution starts here
155 virtual void *Entry();
156
157 // called when the thread exits - whether it terminates normally or is
158 // stopped with Delete() (but not when it is Kill()ed!)
159 virtual void OnExit();
160
161 // write something to the text control
162 void WriteText(const wxString& text);
163
164 public:
165 size_t m_count;
166 MyFrame *m_frame;
167 };
168
169 MyThread::MyThread(MyFrame *frame)
170 : wxThread()
171 {
172 m_count = 0;
173 m_frame = frame;
174 }
175
176 void MyThread::WriteText(const wxString& text)
177 {
178 wxString msg;
179
180 // before doing any GUI calls we must ensure that this thread is the only
181 // one doing it!
182
183 wxMutexGuiEnter();
184
185 msg << text;
186 m_frame->WriteText(msg);
187
188 wxMutexGuiLeave();
189 }
190
191 void MyThread::OnExit()
192 {
193 wxCriticalSectionLocker locker(wxGetApp().m_critsect);
194
195 wxGetApp().m_threads.Remove(this);
196 }
197
198 void *MyThread::Entry()
199 {
200 wxString text;
201
202 text.Printf(wxT("Thread 0x%x started (priority = %d).\n"),
203 GetId(), GetPriority());
204 WriteText(text);
205 // wxLogMessage(text); -- test wxLog thread safeness
206
207 for ( m_count = 0; m_count < 10; m_count++ )
208 {
209 // check if we were asked to exit
210 if ( TestDestroy() )
211 break;
212
213 text.Printf(wxT("[%u] Thread 0x%x here.\n"), m_count, GetId());
214 WriteText(text);
215
216 // wxSleep() can't be called from non-GUI thread!
217 wxThread::Sleep(1000);
218 }
219
220 text.Printf(wxT("Thread 0x%x finished.\n"), GetId());
221 WriteText(text);
222 // wxLogMessage(text); -- test wxLog thread safeness
223
224 return NULL;
225 }
226
227 // ----------------------------------------------------------------------------
228 // worker thread
229 // ----------------------------------------------------------------------------
230
231 class MyWorkerThread : public wxThread
232 {
233 public:
234 MyWorkerThread(MyFrame *frame);
235
236 // thread execution starts here
237 virtual void *Entry();
238
239 // called when the thread exits - whether it terminates normally or is
240 // stopped with Delete() (but not when it is Kill()ed!)
241 virtual void OnExit();
242
243 public:
244 MyFrame *m_frame;
245 size_t m_count;
246 };
247
248 MyWorkerThread::MyWorkerThread(MyFrame *frame)
249 : wxThread()
250 {
251 m_frame = frame;
252 m_count = 0;
253 }
254
255 void MyWorkerThread::OnExit()
256 {
257 }
258
259 void *MyWorkerThread::Entry()
260 {
261 for ( m_count = 0; !m_frame->Cancelled() && (m_count < 100); m_count++ )
262 {
263 // check if we were asked to exit
264 if ( TestDestroy() )
265 break;
266
267 // create any type of command event here
268 wxCommandEvent event( wxEVT_COMMAND_MENU_SELECTED, WORKER_EVENT );
269 event.SetInt( m_count );
270
271 // send in a thread-safe way
272 wxPostEvent( m_frame, event );
273
274 // wxSleep() can't be called from non-main thread!
275 wxThread::Sleep(200);
276 }
277
278 wxCommandEvent event( wxEVT_COMMAND_MENU_SELECTED, WORKER_EVENT );
279 event.SetInt(-1); // that's all
280 wxPostEvent( m_frame, event );
281
282 return NULL;
283 }
284
285 // ----------------------------------------------------------------------------
286 // a thread which simply calls wxExecute
287 // ----------------------------------------------------------------------------
288
289 class MyExecThread : public wxThread
290 {
291 public:
292 MyExecThread(const wxChar *command) : wxThread(wxTHREAD_JOINABLE),
293 m_command(command)
294 {
295 Create();
296 }
297
298 virtual ExitCode Entry()
299 {
300 return (ExitCode)EXEC(m_command);
301 }
302
303 private:
304 wxString m_command;
305 };
306
307 // ----------------------------------------------------------------------------
308 // implementation
309 // ----------------------------------------------------------------------------
310
311 BEGIN_EVENT_TABLE(MyFrame, wxFrame)
312 EVT_MENU(THREAD_QUIT, MyFrame::OnQuit)
313 EVT_MENU(THREAD_CLEAR, MyFrame::OnClear)
314 EVT_MENU(THREAD_START_THREAD, MyFrame::OnStartThread)
315 EVT_MENU(THREAD_START_THREADS, MyFrame::OnStartThreads)
316 EVT_MENU(THREAD_STOP_THREAD, MyFrame::OnStopThread)
317 EVT_MENU(THREAD_PAUSE_THREAD, MyFrame::OnPauseThread)
318 EVT_MENU(THREAD_RESUME_THREAD, MyFrame::OnResumeThread)
319
320 EVT_MENU(THREAD_EXEC_MAIN, MyFrame::OnExecMain)
321 EVT_MENU(THREAD_EXEC_THREAD, MyFrame::OnExecThread)
322
323 EVT_MENU(THREAD_SHOWCPUS, MyFrame::OnShowCPUs)
324 EVT_MENU(THREAD_ABOUT, MyFrame::OnAbout)
325
326 EVT_UPDATE_UI(THREAD_START_WORKER, MyFrame::OnUpdateWorker)
327 EVT_MENU(THREAD_START_WORKER, MyFrame::OnStartWorker)
328 EVT_MENU(WORKER_EVENT, MyFrame::OnWorkerEvent)
329
330 EVT_IDLE(MyFrame::OnIdle)
331 END_EVENT_TABLE()
332
333 // `Main program' equivalent, creating windows and returning main app frame
334 bool MyApp::OnInit()
335 {
336 // uncomment this to get some debugging messages from the trace code
337 // on the console (or just set WXTRACE env variable to include "thread")
338 //wxLog::AddTraceMask("thread");
339
340 // Create the main frame window
341 MyFrame *frame = new MyFrame((wxFrame *)NULL, "wxWindows threads sample",
342 50, 50, 450, 340);
343
344 // Make a menubar
345 wxMenuBar *menuBar = new wxMenuBar;
346
347 wxMenu *menuFile = new wxMenu;
348 menuFile->Append(THREAD_CLEAR, "&Clear log\tCtrl-L");
349 menuFile->AppendSeparator();
350 menuFile->Append(THREAD_QUIT, "E&xit\tAlt-X");
351 menuBar->Append(menuFile, "&File");
352
353 wxMenu *menuThread = new wxMenu;
354 menuThread->Append(THREAD_START_THREAD, "&Start a new thread\tCtrl-N");
355 menuThread->Append(THREAD_START_THREADS, "Start &many threads at once");
356 menuThread->Append(THREAD_STOP_THREAD, "S&top a running thread\tCtrl-S");
357 menuThread->AppendSeparator();
358 menuThread->Append(THREAD_PAUSE_THREAD, "&Pause a running thread\tCtrl-P");
359 menuThread->Append(THREAD_RESUME_THREAD, "&Resume suspended thread\tCtrl-R");
360 menuThread->AppendSeparator();
361 menuThread->Append(THREAD_START_WORKER, "Start &worker thread\tCtrl-W");
362 menuBar->Append(menuThread, "&Thread");
363
364 wxMenu *menuExec = new wxMenu;
365 menuExec->Append(THREAD_EXEC_MAIN, "&Launch a program from main thread\tF5");
366 menuExec->Append(THREAD_EXEC_THREAD, "L&aunch a program from a thread\tCtrl-F5");
367 menuBar->Append(menuExec, "&Execute");
368
369 wxMenu *menuHelp = new wxMenu;
370 menuHelp->Append(THREAD_SHOWCPUS, "&Show CPU count");
371 menuHelp->AppendSeparator();
372 menuHelp->Append(THREAD_ABOUT, "&About...");
373 menuBar->Append(menuHelp, "&Help");
374
375 frame->SetMenuBar(menuBar);
376
377 // Show the frame
378 frame->Show(TRUE);
379
380 SetTopWindow(frame);
381
382 return TRUE;
383 }
384
385 // My frame constructor
386 MyFrame::MyFrame(wxFrame *frame, const wxString& title,
387 int x, int y, int w, int h)
388 : wxFrame(frame, -1, title, wxPoint(x, y), wxSize(w, h))
389 {
390 m_nRunning = m_nCount = 0;
391
392 m_dlgProgress = (wxProgressDialog *)NULL;
393
394 CreateStatusBar(2);
395
396 m_txtctrl = new wxTextCtrl(this, -1, "", wxPoint(0, 0), wxSize(0, 0),
397 wxTE_MULTILINE | wxTE_READONLY);
398
399 }
400
401 MyThread *MyFrame::CreateThread()
402 {
403 MyThread *thread = new MyThread(this);
404
405 if ( thread->Create() != wxTHREAD_NO_ERROR )
406 {
407 wxLogError(wxT("Can't create thread!"));
408 }
409
410 wxCriticalSectionLocker enter(wxGetApp().m_critsect);
411 wxGetApp().m_threads.Add(thread);
412
413 return thread;
414 }
415
416 void MyFrame::OnStartThreads(wxCommandEvent& WXUNUSED(event) )
417 {
418 static long s_num = 10;
419
420 s_num = wxGetNumberFromUser("How many threads to start: ", "",
421 "wxThread sample", s_num, 1, 10000, this);
422 if ( s_num == -1 )
423 {
424 s_num = 10;
425
426 return;
427 }
428
429 size_t count = (size_t)s_num, n;
430
431 wxArrayThread threads;
432
433 // first create them all...
434 for ( n = 0; n < count; n++ )
435 {
436 wxThread *thr = CreateThread();
437
438 // we want to show the effect of SetPriority(): the first thread will
439 // have the lowest priority, the second - the highest, all the rest
440 // the normal one
441 if ( n == 0 )
442 thr->SetPriority(WXTHREAD_MIN_PRIORITY);
443 else if ( n == 1 )
444 thr->SetPriority(WXTHREAD_MAX_PRIORITY);
445 else
446 thr->SetPriority(WXTHREAD_DEFAULT_PRIORITY);
447
448 threads.Add(thr);
449 }
450
451 wxString msg;
452 msg.Printf(wxT("%d new threads created."), count);
453 SetStatusText(msg, 1);
454
455 // ...and then start them
456 for ( n = 0; n < count; n++ )
457 {
458 threads[n]->Run();
459 }
460 }
461
462 void MyFrame::OnStartThread(wxCommandEvent& WXUNUSED(event) )
463 {
464 MyThread *thread = CreateThread();
465
466 if ( thread->Run() != wxTHREAD_NO_ERROR )
467 {
468 wxLogError(wxT("Can't start thread!"));
469 }
470
471 SetStatusText("New thread started.", 1);
472 }
473
474 void MyFrame::OnStopThread(wxCommandEvent& WXUNUSED(event) )
475 {
476 wxGetApp().m_critsect.Enter();
477
478 // stop the last thread
479 if ( wxGetApp().m_threads.IsEmpty() )
480 {
481 wxLogError(wxT("No thread to stop!"));
482
483 wxGetApp().m_critsect.Leave();
484 }
485 else
486 {
487 wxThread *thread = wxGetApp().m_threads.Last();
488
489 // it's important to leave critical section before calling Delete()
490 // because delete will (implicitly) call OnExit() which also tries
491 // to enter the same crit section - would dead lock.
492 wxGetApp().m_critsect.Leave();
493
494 thread->Delete();
495
496 SetStatusText("Thread stopped.", 1);
497 }
498 }
499
500 void MyFrame::OnResumeThread(wxCommandEvent& WXUNUSED(event) )
501 {
502 wxCriticalSectionLocker enter(wxGetApp().m_critsect);
503
504 // resume first suspended thread
505 size_t n = 0, count = wxGetApp().m_threads.Count();
506 while ( n < count && !wxGetApp().m_threads[n]->IsPaused() )
507 n++;
508
509 if ( n == count )
510 {
511 wxLogError(wxT("No thread to resume!"));
512 }
513 else
514 {
515 wxGetApp().m_threads[n]->Resume();
516
517 SetStatusText("Thread resumed.", 1);
518 }
519 }
520
521 void MyFrame::OnPauseThread(wxCommandEvent& WXUNUSED(event) )
522 {
523 wxCriticalSectionLocker enter(wxGetApp().m_critsect);
524
525 // pause last running thread
526 int n = wxGetApp().m_threads.Count() - 1;
527 while ( n >= 0 && !wxGetApp().m_threads[n]->IsRunning() )
528 n--;
529
530 if ( n < 0 )
531 {
532 wxLogError(wxT("No thread to pause!"));
533 }
534 else
535 {
536 wxGetApp().m_threads[n]->Pause();
537
538 SetStatusText("Thread paused.", 1);
539 }
540 }
541
542 // set the frame title indicating the current number of threads
543 void MyFrame::OnIdle(wxIdleEvent &event)
544 {
545 // update the counts of running/total threads
546 size_t nRunning = 0,
547 nCount = wxGetApp().m_threads.Count();
548 for ( size_t n = 0; n < nCount; n++ )
549 {
550 if ( wxGetApp().m_threads[n]->IsRunning() )
551 nRunning++;
552 }
553
554 if ( nCount != m_nCount || nRunning != m_nRunning )
555 {
556 m_nRunning = nRunning;
557 m_nCount = nCount;
558
559 wxLogStatus(this, wxT("%u threads total, %u running."), nCount, nRunning);
560 }
561 //else: avoid flicker - don't print anything
562 }
563
564 void MyFrame::OnQuit(wxCommandEvent& WXUNUSED(event) )
565 {
566 size_t count = wxGetApp().m_threads.Count();
567 for ( size_t i = 0; i < count; i++ )
568 {
569 wxGetApp().m_threads[0]->Delete();
570 }
571
572 Close(TRUE);
573 }
574
575 void MyFrame::OnExecMain(wxCommandEvent& WXUNUSED(event))
576 {
577 wxLogMessage("The exit code from the main program is %ld",
578 EXEC("/bin/echo \"main program\""));
579 }
580
581 void MyFrame::OnExecThread(wxCommandEvent& WXUNUSED(event))
582 {
583 MyExecThread thread("/bin/echo \"child thread\"");
584 thread.Run();
585
586 wxLogMessage("The exit code from a child thread is %ld",
587 (long)thread.Wait());
588 }
589
590 void MyFrame::OnShowCPUs(wxCommandEvent& WXUNUSED(event))
591 {
592 wxString msg;
593
594 int nCPUs = wxThread::GetCPUCount();
595 switch ( nCPUs )
596 {
597 case -1:
598 msg = "Unknown number of CPUs";
599 break;
600
601 case 0:
602 msg = "WARNING: you're running without any CPUs!";
603 break;
604
605 case 1:
606 msg = "This system only has one CPU.";
607 break;
608
609 default:
610 msg.Printf("This system has %d CPUs", nCPUs);
611 }
612
613 wxLogMessage(msg);
614 }
615
616 void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event) )
617 {
618 wxMessageDialog dialog(this, "wxWindows multithreaded application sample\n"
619 "(c) 1998 Julian Smart, Guilhem Lavaux\n"
620 "(c) 1999 Vadim Zeitlin\n"
621 "(c) 2000 Robert Roebling",
622 "About wxThread sample",
623 wxOK | wxICON_INFORMATION);
624
625 dialog.ShowModal();
626 }
627
628 void MyFrame::OnClear(wxCommandEvent& WXUNUSED(event))
629 {
630 m_txtctrl->Clear();
631 }
632
633 void MyFrame::OnUpdateWorker(wxUpdateUIEvent& event)
634 {
635 event.Enable( m_dlgProgress == NULL );
636 }
637
638 void MyFrame::OnStartWorker(wxCommandEvent& WXUNUSED(event))
639 {
640 MyWorkerThread *thread = new MyWorkerThread(this);
641
642 if ( thread->Create() != wxTHREAD_NO_ERROR )
643 {
644 wxLogError(wxT("Can't create thread!"));
645 }
646
647 m_dlgProgress = new wxProgressDialog
648 (
649 "Progress dialog",
650 "Wait until the thread terminates or press [Cancel]",
651 100,
652 this,
653 wxPD_CAN_ABORT |
654 wxPD_APP_MODAL |
655 wxPD_ELAPSED_TIME |
656 wxPD_ESTIMATED_TIME |
657 wxPD_REMAINING_TIME
658 );
659
660 // thread is not running yet, no need for crit sect
661 m_cancelled = FALSE;
662
663 thread->Run();
664 }
665
666 void MyFrame::OnWorkerEvent(wxCommandEvent& event)
667 {
668 #if 0
669 WriteText( "Got message from worker thread: " );
670 WriteText( event.GetString() );
671 WriteText( "\n" );
672 #else
673 int n = event.GetInt();
674 if ( n == -1 )
675 {
676 m_dlgProgress->Destroy();
677 m_dlgProgress = (wxProgressDialog *)NULL;
678
679 // the dialog is aborted because the event came from another thread, so
680 // we may need to wake up the main event loop for the dialog to be
681 // really closed
682 wxWakeUpIdle();
683 }
684 else
685 {
686 if ( !m_dlgProgress->Update(n) )
687 {
688 wxCriticalSectionLocker lock(m_critsectWork);
689
690 m_cancelled = TRUE;
691 }
692 }
693 #endif
694 }
695
696 bool MyFrame::Cancelled()
697 {
698 wxCriticalSectionLocker lock(m_critsectWork);
699
700 return m_cancelled;
701 }