]> git.saurik.com Git - wxWidgets.git/blob - src/os2/thread.cpp
Style updates
[wxWidgets.git] / src / os2 / thread.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: thread.cpp
3 // Purpose: wxThread Implementation. For Unix ports, see e.g. src/gtk
4 // Author: Original from Wolfram Gloger/Guilhem Lavaux
5 // Modified by: David Webster
6 // Created: 04/22/98
7 // RCS-ID: $Id$
8 // Copyright: (c) Wolfram Gloger (1996, 1997); Guilhem Lavaux (1998)
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 // ----------------------------------------------------------------------------
13 // headers
14 // ----------------------------------------------------------------------------
15
16 // For compilers that support precompilation, includes "wx.h".
17 #include "wx/wxprec.h"
18
19 #if wxUSE_THREADS
20
21 #include <stdio.h>
22
23 #include "wx/module.h"
24 #include "wx/intl.h"
25 #include "wx/utils.h"
26 #include "wx/log.h"
27 #include "wx/thread.h"
28
29 #define INCL_DOSSEMAPHORES
30 #define INCL_DOSPROCESS
31 #define INCL_ERRORS
32 #include <os2.h>
33 #include <bseerr.h>
34
35 // the possible states of the thread ("=>" shows all possible transitions from
36 // this state)
37 enum wxThreadState
38 {
39 STATE_NEW, // didn't start execution yet (=> RUNNING)
40 STATE_RUNNING, // thread is running (=> PAUSED, CANCELED)
41 STATE_PAUSED, // thread is temporarily suspended (=> RUNNING)
42 STATE_CANCELED, // thread should terminate a.s.a.p. (=> EXITED)
43 STATE_EXITED // thread is terminating
44 };
45
46 // ----------------------------------------------------------------------------
47 // static variables
48 // ----------------------------------------------------------------------------
49
50 // id of the main thread - the one which can call GUI functions without first
51 // calling wxMutexGuiEnter()
52 static ULONG s_ulIdMainThread = 0;
53 wxMutex* p_wxMainMutex;
54
55 // OS2 substitute for Tls pointer the current parent thread object
56 wxThread* m_pThread; // pointer to the wxWindows thread object
57
58 // if it's FALSE, some secondary thread is holding the GUI lock
59 static bool gs_bGuiOwnedByMainThread = TRUE;
60
61 // critical section which controls access to all GUI functions: any secondary
62 // thread (i.e. except the main one) must enter this crit section before doing
63 // any GUI calls
64 static wxCriticalSection *gs_pCritsectGui = NULL;
65
66 // critical section which protects s_nWaitingForGui variable
67 static wxCriticalSection *gs_pCritsectWaitingForGui = NULL;
68
69 // number of threads waiting for GUI in wxMutexGuiEnter()
70 static size_t gs_nWaitingForGui = 0;
71
72 // are we waiting for a thread termination?
73 static bool gs_bWaitingForThread = FALSE;
74
75 // ============================================================================
76 // OS/2 implementation of thread classes
77 // ============================================================================
78
79 // ----------------------------------------------------------------------------
80 // wxMutex implementation
81 // ----------------------------------------------------------------------------
82 class wxMutexInternal
83 {
84 public:
85 HMTX m_vMutex;
86 };
87
88 wxMutex::wxMutex()
89 {
90 APIRET ulrc;
91
92 m_internal = new wxMutexInternal;
93 ulrc = ::DosCreateMutexSem(NULL, &m_internal->m_vMutex, 0L, FALSE);
94 if (ulrc != 0)
95 {
96 wxLogSysError(_("Can not create mutex."));
97 }
98 m_locked = 0;
99 }
100
101 wxMutex::~wxMutex()
102 {
103 if (m_locked > 0)
104 wxLogDebug(wxT("Warning: freeing a locked mutex (%d locks)."), m_locked);
105 ::DosCloseMutexSem(m_internal->m_vMutex);
106 m_internal->m_vMutex = NULL;
107 }
108
109 wxMutexError wxMutex::Lock()
110 {
111 APIRET ulrc;
112
113 ulrc = ::DosRequestMutexSem(m_internal->m_vMutex, SEM_INDEFINITE_WAIT);
114
115 switch (ulrc)
116 {
117 case ERROR_TOO_MANY_SEM_REQUESTS:
118 return wxMUTEX_BUSY;
119
120 case NO_ERROR:
121 // ok
122 break;
123
124 case ERROR_INVALID_HANDLE:
125 case ERROR_INTERRUPT:
126 case ERROR_SEM_OWNER_DIED:
127 wxLogSysError(_("Couldn't acquire a mutex lock"));
128 return wxMUTEX_MISC_ERROR;
129
130 case ERROR_TIMEOUT:
131 default:
132 wxFAIL_MSG(wxT("impossible return value in wxMutex::Lock"));
133 }
134 m_locked++;
135 return wxMUTEX_NO_ERROR;
136 }
137
138 wxMutexError wxMutex::TryLock()
139 {
140 ULONG ulrc;
141
142 ulrc = ::DosRequestMutexSem(m_internal->m_vMutex, SEM_IMMEDIATE_RETURN /*0L*/);
143 if (ulrc == ERROR_TIMEOUT || ulrc == ERROR_TOO_MANY_SEM_REQUESTS)
144 return wxMUTEX_BUSY;
145
146 m_locked++;
147 return wxMUTEX_NO_ERROR;
148 }
149
150 wxMutexError wxMutex::Unlock()
151 {
152 APIRET ulrc;
153
154 if (m_locked > 0)
155 m_locked--;
156
157 ulrc = ::DosReleaseMutexSem(m_internal->m_vMutex);
158 if (ulrc != 0)
159 {
160 wxLogSysError(_("Couldn't release a mutex"));
161 return wxMUTEX_MISC_ERROR;
162 }
163 return wxMUTEX_NO_ERROR;
164 }
165
166 // ----------------------------------------------------------------------------
167 // wxCondition implementation
168 // ----------------------------------------------------------------------------
169
170 class wxConditionInternal
171 {
172 public:
173 inline wxConditionInternal ()
174 {
175 ::DosCreateEventSem(NULL, &m_vEvent, DC_SEM_SHARED, FALSE);
176 if (!m_vEvent)
177 {
178 wxLogSysError(_("Can not create event semaphore."));
179 }
180 m_nWaiters = 0;
181 }
182
183 inline bool Wait(
184 unsigned long ulTimeout
185 )
186 {
187 APIRET ulrc;
188
189 m_nWaiters++;
190 ulrc = ::DosWaitEventSem(m_vEvent, ulTimeout);
191 m_nWaiters--;
192 return (ulrc != ERROR_TIMEOUT);
193 }
194
195 inline ~wxConditionInternal ()
196 {
197 APIRET ulrc;
198
199 if (m_vEvent)
200 {
201 ulrc = ::DosCloseEventSem(m_vEvent);
202 if (!ulrc)
203 {
204 wxLogLastError("DosCloseEventSem(m_vEvent)");
205 }
206 }
207 }
208
209 HEV m_vEvent;
210 int m_nWaiters;
211 };
212
213 wxCondition::wxCondition()
214 {
215 APIRET ulrc;
216 ULONG ulCount;
217
218 m_internal = new wxConditionInternal;
219 ulrc = ::DosCreateEventSem(NULL, &m_internal->m_vEvent, 0L, FALSE);
220 if (ulrc != 0)
221 {
222 wxLogSysError(_("Can not create event object."));
223 }
224 m_internal->m_nWaiters = 0;
225 // ?? just for good measure?
226 ::DosResetEventSem(m_internal->m_vEvent, &ulCount);
227 }
228
229 wxCondition::~wxCondition()
230 {
231 ::DosCloseEventSem(m_internal->m_vEvent);
232 delete m_internal;
233 m_internal = NULL;
234 }
235
236 void wxCondition::Wait()
237 {
238 (void)m_internal->Wait(SEM_INDEFINITE_WAIT);
239 }
240
241 bool wxCondition::Wait(
242 unsigned long lSec
243 , unsigned long lNsec)
244 {
245 return m_internal->Wait(lSec*1000 + lNsec/1000000);
246 }
247
248 void wxCondition::Signal()
249 {
250 ::DosPostEventSem(m_internal->m_vEvent);
251 }
252
253 void wxCondition::Broadcast()
254 {
255 int i;
256
257 for (i = 0; i < m_internal->m_nWaiters; i++)
258 {
259 if (::DosPostEventSem(m_internal->m_vEvent) != 0)
260 {
261 wxLogSysError(_("Couldn't change the state of event object."));
262 }
263 }
264 }
265
266 // ----------------------------------------------------------------------------
267 // wxCriticalSection implementation
268 // ----------------------------------------------------------------------------
269
270 wxCriticalSection::wxCriticalSection()
271 {
272 }
273
274 wxCriticalSection::~wxCriticalSection()
275 {
276 }
277
278 void wxCriticalSection::Enter()
279 {
280 ::DosEnterCritSec();
281 }
282
283 void wxCriticalSection::Leave()
284 {
285 ::DosExitCritSec();
286 }
287
288 // ----------------------------------------------------------------------------
289 // wxThread implementation
290 // ----------------------------------------------------------------------------
291
292 // wxThreadInternal class
293 // ----------------------
294
295 class wxThreadInternal
296 {
297 public:
298 inline wxThreadInternal()
299 {
300 m_hThread = 0;
301 m_eState = STATE_NEW;
302 m_nPriority = 0;
303 }
304
305 ~wxThreadInternal()
306 {
307 Free();
308 }
309
310 void Free()
311 {
312 if (m_hThread)
313 {
314 ::DosExit(0,0);
315 m_hThread = 0;
316 }
317 }
318
319 // create a new (suspended) thread (for the given thread object)
320 bool Create( wxThread* pThread
321 ,unsigned int uStackSize
322 );
323
324 // suspend/resume/terminate
325 bool Suspend();
326 bool Resume();
327 inline void Cancel() { m_eState = STATE_CANCELED; }
328
329 // thread state
330 inline void SetState(wxThreadState eState) { m_eState = eState; }
331 inline wxThreadState GetState() const { return m_eState; }
332
333 // thread priority
334 void SetPriority(unsigned int nPriority);
335 inline unsigned int GetPriority() const { return m_nPriority; }
336
337 // thread handle and id
338 inline TID GetHandle() const { return m_hThread; }
339 TID GetId() const { return m_hThread; }
340
341 // thread function
342 static DWORD OS2ThreadStart(wxThread *thread);
343
344 private:
345 // Threads in OS/2 have only an ID, so m_hThread is both it's handle and ID
346 // PM also has no real Tls mechanism to index pointers by so we'll just
347 // keep track of the wxWindows parent object here.
348 TID m_hThread; // handle and ID of the thread
349 wxThreadState m_eState; // state, see wxThreadState enum
350 unsigned int m_nPriority; // thread priority in "wx" units
351 };
352
353 ULONG wxThreadInternal::OS2ThreadStart(
354 wxThread* pThread
355 )
356 {
357 m_pThread = pThread;
358
359 DWORD dwRet = (DWORD)pThread->Entry();
360
361 // enter m_critsect before changing the thread state
362 pThread->m_critsect.Enter();
363
364 bool bWasCancelled = pThread->m_internal->GetState() == STATE_CANCELED;
365
366 pThread->m_internal->SetState(STATE_EXITED);
367 pThread->m_critsect.Leave();
368
369 pThread->OnExit();
370
371 // if the thread was cancelled (from Delete()), then it the handle is still
372 // needed there
373 if (pThread->IsDetached() && !bWasCancelled)
374 {
375 // auto delete
376 delete pThread;
377 }
378 //else: the joinable threads handle will be closed when Wait() is done
379 return dwRet;
380 }
381
382 void wxThreadInternal::SetPriority(
383 unsigned int nPriority
384 )
385 {
386 // translate wxWindows priority to the PM one
387 ULONG ulOS2_Priority;
388 ULONG ulrc;
389
390 m_nPriority = nPriority;
391
392 if (m_nPriority <= 20)
393 ulOS2_Priority = PRTYC_NOCHANGE;
394 else if (m_nPriority <= 40)
395 ulOS2_Priority = PRTYC_IDLETIME;
396 else if (m_nPriority <= 60)
397 ulOS2_Priority = PRTYC_REGULAR;
398 else if (m_nPriority <= 80)
399 ulOS2_Priority = PRTYC_TIMECRITICAL;
400 else if (m_nPriority <= 100)
401 ulOS2_Priority = PRTYC_FOREGROUNDSERVER;
402 else
403 {
404 wxFAIL_MSG(wxT("invalid value of thread priority parameter"));
405 ulOS2_Priority = PRTYC_REGULAR;
406 }
407 ulrc = ::DosSetPriority( PRTYS_THREAD
408 ,ulOS2_Priority
409 ,0
410 ,(ULONG)m_hThread
411 );
412 if (ulrc != 0)
413 {
414 wxLogSysError(_("Can't set thread priority"));
415 }
416 }
417
418 bool wxThreadInternal::Create(
419 wxThread* pThread
420 , unsigned int uStackSize
421 )
422 {
423 APIRET ulrc;
424
425 ulrc = ::DosCreateThread( &m_hThread
426 ,(PFNTHREAD)wxThreadInternal::OS2ThreadStart
427 ,(ULONG)pThread
428 ,CREATE_SUSPENDED | STACK_SPARSE
429 ,(ULONG)uStackSize
430 );
431 if(ulrc != 0)
432 {
433 wxLogSysError(_("Can't create thread"));
434
435 return FALSE;
436 }
437 if (m_nPriority != WXTHREAD_DEFAULT_PRIORITY)
438 {
439 SetPriority(m_nPriority);
440 }
441 return(TRUE);
442 }
443
444 bool wxThreadInternal::Suspend()
445 {
446 ULONG ulrc = ::DosSuspendThread(m_hThread);
447
448 if (ulrc != 0)
449 {
450 wxLogSysError(_("Can not suspend thread %lu"), m_hThread);
451 return FALSE;
452 }
453 m_eState = STATE_PAUSED;
454 return TRUE;
455 }
456
457 bool wxThreadInternal::Resume()
458 {
459 ULONG ulrc = ::DosResumeThread(m_hThread);
460
461 if (ulrc != 0)
462 {
463 wxLogSysError(_("Can not suspend thread %lu"), m_hThread);
464 return FALSE;
465 }
466 m_eState = STATE_PAUSED;
467 return TRUE;
468 }
469
470 // static functions
471 // ----------------
472
473 wxThread *wxThread::This()
474 {
475 wxThread* pThread = m_pThread;
476 return pThread;
477 }
478
479 bool wxThread::IsMain()
480 {
481 PTIB ptib;
482 PPIB ppib;
483
484 ::DosGetInfoBlocks(&ptib, &ppib);
485
486 if (ptib->tib_ptib2->tib2_ultid == s_ulIdMainThread)
487 return TRUE;
488 return FALSE;
489 }
490
491 #ifdef Yield
492 #undef Yield
493 #endif
494
495 void wxThread::Yield()
496 {
497 ::DosSleep(0);
498 }
499
500 void wxThread::Sleep(
501 unsigned long ulMilliseconds
502 )
503 {
504 ::DosSleep(ulMilliseconds);
505 }
506
507 // ctor and dtor
508 // -------------
509
510 wxThread::wxThread(wxThreadKind kind)
511 {
512 m_internal = new wxThreadInternal();
513
514 m_isDetached = kind == wxTHREAD_DETACHED;
515 }
516
517 wxThread::~wxThread()
518 {
519 delete m_internal;
520 }
521
522 // create/start thread
523 // -------------------
524
525 wxThreadError wxThread::Create(
526 unsigned int uStackSize
527 )
528 {
529 if ( !m_internal->Create(this, uStackSize) )
530 return wxTHREAD_NO_RESOURCE;
531
532 return wxTHREAD_NO_ERROR;
533 }
534
535 wxThreadError wxThread::Run()
536 {
537 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
538
539 if ( m_internal->GetState() != STATE_NEW )
540 {
541 // actually, it may be almost any state at all, not only STATE_RUNNING
542 return wxTHREAD_RUNNING;
543 }
544 return Resume();
545 }
546
547 // suspend/resume thread
548 // ---------------------
549
550 wxThreadError wxThread::Pause()
551 {
552 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
553
554 return m_internal->Suspend() ? wxTHREAD_NO_ERROR : wxTHREAD_MISC_ERROR;
555 }
556
557 wxThreadError wxThread::Resume()
558 {
559 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
560
561 return m_internal->Resume() ? wxTHREAD_NO_ERROR : wxTHREAD_MISC_ERROR;
562 }
563
564 // stopping thread
565 // ---------------
566
567 wxThread::ExitCode wxThread::Wait()
568 {
569 // although under Windows we can wait for any thread, it's an error to
570 // wait for a detached one in wxWin API
571 wxCHECK_MSG( !IsDetached(), (ExitCode)-1,
572 _T("can't wait for detached thread") );
573 ExitCode rc = (ExitCode)-1;
574 (void)Delete(&rc);
575 m_internal->Free();
576 return(rc);
577 }
578
579 wxThreadError wxThread::Delete(ExitCode *pRc)
580 {
581 ExitCode rc = 0;
582
583 // Delete() is always safe to call, so consider all possible states
584 if (IsPaused())
585 Resume();
586
587 TID hThread = m_internal->GetHandle();
588
589 if (IsRunning())
590 {
591 if (IsMain())
592 {
593 // set flag for wxIsWaitingForThread()
594 gs_bWaitingForThread = TRUE;
595
596 #if wxUSE_GUI
597 wxBeginBusyCursor();
598 #endif // wxUSE_GUI
599 }
600
601 // ask the thread to terminate
602 {
603 wxCriticalSectionLocker lock(m_critsect);
604 m_internal->Cancel();
605 }
606
607 #if wxUSE_GUI
608 // need a way to finish GUI processing before killing the thread
609 // until then we just exit
610
611 if ((gs_nWaitingForGui > 0) && wxGuiOwnedByMainThread())
612 {
613 wxMutexGuiLeave();
614 }
615 #else // !wxUSE_GUI
616
617 // can't wait for yourself to end under OS/2 so just quit
618
619 #endif // wxUSE_GUI/!wxUSE_GUI
620
621 if ( IsMain() )
622 {
623 gs_bWaitingForThread = FALSE;
624
625 #if wxUSE_GUI
626 wxEndBusyCursor();
627 #endif // wxUSE_GUI
628 }
629 }
630
631 ::DosExit(0, 0);
632 // probably won't get this far, but
633 if (IsDetached())
634 {
635 delete this;
636 }
637
638 if ( pRc )
639 *pRc = rc;
640
641 return rc == (ExitCode)-1 ? wxTHREAD_MISC_ERROR : wxTHREAD_NO_ERROR;
642 }
643
644 wxThreadError wxThread::Kill()
645 {
646 if (!IsRunning())
647 return wxTHREAD_NOT_RUNNING;
648
649 ::DosKillThread(m_internal->GetHandle());
650 m_internal->Free();
651 if (IsDetached())
652 {
653 delete this;
654 }
655 return wxTHREAD_NO_ERROR;
656 }
657
658 void wxThread::Exit(
659 ExitCode pStatus
660 )
661 {
662 m_internal->Free();
663 delete this;
664 ::DosExit(EXIT_THREAD, ULONG(pStatus));
665 wxFAIL_MSG(wxT("Couldn't return from DosExit()!"));
666 }
667
668 void wxThread::SetPriority(
669 unsigned int nPrio
670 )
671 {
672 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
673
674 m_internal->SetPriority(nPrio);
675 }
676
677 unsigned int wxThread::GetPriority() const
678 {
679 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
680
681 return m_internal->GetPriority();
682 }
683
684 unsigned long wxThread::GetId() const
685 {
686 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect); // const_cast
687
688 return (unsigned long)m_internal->GetId();
689 }
690
691 bool wxThread::IsRunning() const
692 {
693 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
694
695 return(m_internal->GetState() == STATE_RUNNING);
696 }
697
698 bool wxThread::IsAlive() const
699 {
700 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
701
702 return (m_internal->GetState() == STATE_RUNNING) ||
703 (m_internal->GetState() == STATE_PAUSED);
704 }
705
706 bool wxThread::IsPaused() const
707 {
708 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
709
710 return (m_internal->GetState() == STATE_PAUSED);
711 }
712
713 bool wxThread::TestDestroy()
714 {
715 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
716
717 return m_internal->GetState() == STATE_CANCELED;
718 }
719
720 // ----------------------------------------------------------------------------
721 // Automatic initialization for thread module
722 // ----------------------------------------------------------------------------
723
724 class wxThreadModule : public wxModule
725 {
726 public:
727 virtual bool OnInit();
728 virtual void OnExit();
729
730 private:
731 DECLARE_DYNAMIC_CLASS(wxThreadModule)
732 };
733
734 IMPLEMENT_DYNAMIC_CLASS(wxThreadModule, wxModule)
735
736 bool wxThreadModule::OnInit()
737 {
738 gs_pCritsectWaitingForGui = new wxCriticalSection();
739
740 gs_pCritsectGui = new wxCriticalSection();
741 gs_pCritsectGui->Enter();
742
743 PTIB ptib;
744 PPIB ppib;
745
746 ::DosGetInfoBlocks(&ptib, &ppib);
747
748 s_ulIdMainThread = ptib->tib_ptib2->tib2_ultid;
749 return TRUE;
750 }
751
752 void wxThreadModule::OnExit()
753 {
754 if (gs_pCritsectGui)
755 {
756 gs_pCritsectGui->Leave();
757 #if (!(defined(__VISAGECPP__) && (__IBMCPP__ < 400 || __IBMC__ < 400 )))
758 delete gs_pCritsectGui;
759 #endif
760 gs_pCritsectGui = NULL;
761 }
762
763 #if (!(defined(__VISAGECPP__) && (__IBMCPP__ < 400 || __IBMC__ < 400 )))
764 wxDELETE(gs_pCritsectWaitingForGui);
765 #endif
766 }
767
768 // ----------------------------------------------------------------------------
769 // Helper functions
770 // ----------------------------------------------------------------------------
771
772 // Does nothing under OS/2 [for now]
773 void WXDLLEXPORT wxWakeUpMainThread()
774 {
775 }
776
777 void WXDLLEXPORT wxMutexGuiLeave()
778 {
779 wxCriticalSectionLocker enter(*gs_pCritsectWaitingForGui);
780
781 if ( wxThread::IsMain() )
782 {
783 gs_bGuiOwnedByMainThread = FALSE;
784 }
785 else
786 {
787 // decrement the number of waiters now
788 wxASSERT_MSG(gs_nWaitingForGui > 0,
789 wxT("calling wxMutexGuiLeave() without entering it first?") );
790
791 gs_nWaitingForGui--;
792
793 wxWakeUpMainThread();
794 }
795
796 gs_pCritsectGui->Leave();
797 }
798
799 void WXDLLEXPORT wxMutexGuiLeaveOrEnter()
800 {
801 wxASSERT_MSG( wxThread::IsMain(),
802 wxT("only main thread may call wxMutexGuiLeaveOrEnter()!") );
803
804 wxCriticalSectionLocker enter(*gs_pCritsectWaitingForGui);
805
806 if (gs_nWaitingForGui == 0)
807 {
808 // no threads are waiting for GUI - so we may acquire the lock without
809 // any danger (but only if we don't already have it)
810 if (!wxGuiOwnedByMainThread())
811 {
812 gs_pCritsectGui->Enter();
813
814 gs_bGuiOwnedByMainThread = TRUE;
815 }
816 //else: already have it, nothing to do
817 }
818 else
819 {
820 // some threads are waiting, release the GUI lock if we have it
821 if (wxGuiOwnedByMainThread())
822 {
823 wxMutexGuiLeave();
824 }
825 //else: some other worker thread is doing GUI
826 }
827 }
828
829 bool WXDLLEXPORT wxGuiOwnedByMainThread()
830 {
831 return gs_bGuiOwnedByMainThread;
832 }
833
834 bool WXDLLEXPORT wxIsWaitingForThread()
835 {
836 return gs_bWaitingForThread;
837 }
838
839 #endif
840 // wxUSE_THREADS