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