]> git.saurik.com Git - wxWidgets.git/blob - src/os2/thread.cpp
survive delete within Notify
[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 (wxMutex& rMutex) : m_vMutex(rMutex)
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 wxMutex& m_vMutex;
212 };
213
214 wxCondition::wxCondition(wxMutex& rMutex)
215 {
216 APIRET ulrc;
217 ULONG ulCount;
218
219 m_internal = new wxConditionInternal(rMutex);
220 ulrc = ::DosCreateEventSem(NULL, &m_internal->m_vEvent, 0L, FALSE);
221 if (ulrc != 0)
222 {
223 wxLogSysError(_("Can not create event object."));
224 }
225 m_internal->m_nWaiters = 0;
226 // ?? just for good measure?
227 ::DosResetEventSem(m_internal->m_vEvent, &ulCount);
228 }
229
230 wxCondition::~wxCondition()
231 {
232 ::DosCloseEventSem(m_internal->m_vEvent);
233 delete m_internal;
234 m_internal = NULL;
235 }
236
237 void wxCondition::Wait()
238 {
239 (void)m_internal->Wait(SEM_INDEFINITE_WAIT);
240 }
241
242 bool wxCondition::Wait(
243 unsigned long lMilliSec
244 )
245 {
246 return m_internal->Wait(lMilliSec);
247 }
248
249 void wxCondition::Signal()
250 {
251 ::DosPostEventSem(m_internal->m_vEvent);
252 }
253
254 void wxCondition::Broadcast()
255 {
256 int i;
257
258 for (i = 0; i < m_internal->m_nWaiters; i++)
259 {
260 if (::DosPostEventSem(m_internal->m_vEvent) != 0)
261 {
262 wxLogSysError(_("Couldn't change the state of event object."));
263 }
264 }
265 }
266
267 // ----------------------------------------------------------------------------
268 // wxCriticalSection implementation
269 // ----------------------------------------------------------------------------
270
271 wxCriticalSection::wxCriticalSection()
272 {
273 }
274
275 wxCriticalSection::~wxCriticalSection()
276 {
277 }
278
279 void wxCriticalSection::Enter()
280 {
281 ::DosEnterCritSec();
282 }
283
284 void wxCriticalSection::Leave()
285 {
286 ::DosExitCritSec();
287 }
288
289 // ----------------------------------------------------------------------------
290 // wxThread implementation
291 // ----------------------------------------------------------------------------
292
293 // wxThreadInternal class
294 // ----------------------
295
296 class wxThreadInternal
297 {
298 public:
299 inline wxThreadInternal()
300 {
301 m_hThread = 0;
302 m_eState = STATE_NEW;
303 m_nPriority = 0;
304 }
305
306 ~wxThreadInternal()
307 {
308 Free();
309 }
310
311 void Free()
312 {
313 if (m_hThread)
314 {
315 ::DosExit(0,0);
316 m_hThread = 0;
317 }
318 }
319
320 // create a new (suspended) thread (for the given thread object)
321 bool Create( wxThread* pThread
322 ,unsigned int uStackSize
323 );
324
325 // suspend/resume/terminate
326 bool Suspend();
327 bool Resume();
328 inline void Cancel() { m_eState = STATE_CANCELED; }
329
330 // thread state
331 inline void SetState(wxThreadState eState) { m_eState = eState; }
332 inline wxThreadState GetState() const { return m_eState; }
333
334 // thread priority
335 void SetPriority(unsigned int nPriority);
336 inline unsigned int GetPriority() const { return m_nPriority; }
337
338 // thread handle and id
339 inline TID GetHandle() const { return m_hThread; }
340 TID GetId() const { return m_hThread; }
341
342 // thread function
343 static DWORD OS2ThreadStart(wxThread *thread);
344
345 private:
346 // Threads in OS/2 have only an ID, so m_hThread is both it's handle and ID
347 // PM also has no real Tls mechanism to index pointers by so we'll just
348 // keep track of the wxWindows parent object here.
349 TID m_hThread; // handle and ID of the thread
350 wxThreadState m_eState; // state, see wxThreadState enum
351 unsigned int m_nPriority; // thread priority in "wx" units
352 };
353
354 ULONG wxThreadInternal::OS2ThreadStart(
355 wxThread* pThread
356 )
357 {
358 m_pThread = pThread;
359
360 DWORD dwRet = (DWORD)pThread->Entry();
361
362 // enter m_critsect before changing the thread state
363 pThread->m_critsect.Enter();
364
365 bool bWasCancelled = pThread->m_internal->GetState() == STATE_CANCELED;
366
367 pThread->m_internal->SetState(STATE_EXITED);
368 pThread->m_critsect.Leave();
369
370 pThread->OnExit();
371
372 // if the thread was cancelled (from Delete()), then it the handle is still
373 // needed there
374 if (pThread->IsDetached() && !bWasCancelled)
375 {
376 // auto delete
377 delete pThread;
378 }
379 //else: the joinable threads handle will be closed when Wait() is done
380 return dwRet;
381 }
382
383 void wxThreadInternal::SetPriority(
384 unsigned int nPriority
385 )
386 {
387 // translate wxWindows priority to the PM one
388 ULONG ulOS2_Priority;
389 ULONG ulrc;
390
391 m_nPriority = nPriority;
392
393 if (m_nPriority <= 20)
394 ulOS2_Priority = PRTYC_NOCHANGE;
395 else if (m_nPriority <= 40)
396 ulOS2_Priority = PRTYC_IDLETIME;
397 else if (m_nPriority <= 60)
398 ulOS2_Priority = PRTYC_REGULAR;
399 else if (m_nPriority <= 80)
400 ulOS2_Priority = PRTYC_TIMECRITICAL;
401 else if (m_nPriority <= 100)
402 ulOS2_Priority = PRTYC_FOREGROUNDSERVER;
403 else
404 {
405 wxFAIL_MSG(wxT("invalid value of thread priority parameter"));
406 ulOS2_Priority = PRTYC_REGULAR;
407 }
408 ulrc = ::DosSetPriority( PRTYS_THREAD
409 ,ulOS2_Priority
410 ,0
411 ,(ULONG)m_hThread
412 );
413 if (ulrc != 0)
414 {
415 wxLogSysError(_("Can't set thread priority"));
416 }
417 }
418
419 bool wxThreadInternal::Create(
420 wxThread* pThread
421 , unsigned int uStackSize
422 )
423 {
424 APIRET ulrc;
425
426 ulrc = ::DosCreateThread( &m_hThread
427 ,(PFNTHREAD)wxThreadInternal::OS2ThreadStart
428 ,(ULONG)pThread
429 ,CREATE_SUSPENDED | STACK_SPARSE
430 ,(ULONG)uStackSize
431 );
432 if(ulrc != 0)
433 {
434 wxLogSysError(_("Can't create thread"));
435
436 return FALSE;
437 }
438 if (m_nPriority != WXTHREAD_DEFAULT_PRIORITY)
439 {
440 SetPriority(m_nPriority);
441 }
442 return(TRUE);
443 }
444
445 bool wxThreadInternal::Suspend()
446 {
447 ULONG ulrc = ::DosSuspendThread(m_hThread);
448
449 if (ulrc != 0)
450 {
451 wxLogSysError(_("Can not suspend thread %lu"), m_hThread);
452 return FALSE;
453 }
454 m_eState = STATE_PAUSED;
455 return TRUE;
456 }
457
458 bool wxThreadInternal::Resume()
459 {
460 ULONG ulrc = ::DosResumeThread(m_hThread);
461
462 if (ulrc != 0)
463 {
464 wxLogSysError(_("Can not suspend thread %lu"), m_hThread);
465 return FALSE;
466 }
467 m_eState = STATE_PAUSED;
468 return TRUE;
469 }
470
471 // static functions
472 // ----------------
473
474 wxThread *wxThread::This()
475 {
476 wxThread* pThread = m_pThread;
477 return pThread;
478 }
479
480 bool wxThread::IsMain()
481 {
482 PTIB ptib;
483 PPIB ppib;
484
485 ::DosGetInfoBlocks(&ptib, &ppib);
486
487 if (ptib->tib_ptib2->tib2_ultid == s_ulIdMainThread)
488 return TRUE;
489 return FALSE;
490 }
491
492 #ifdef Yield
493 #undef Yield
494 #endif
495
496 void wxThread::Yield()
497 {
498 ::DosSleep(0);
499 }
500
501 void wxThread::Sleep(
502 unsigned long ulMilliseconds
503 )
504 {
505 ::DosSleep(ulMilliseconds);
506 }
507
508 // ctor and dtor
509 // -------------
510
511 wxThread::wxThread(wxThreadKind kind)
512 {
513 m_internal = new wxThreadInternal();
514
515 m_isDetached = kind == wxTHREAD_DETACHED;
516 }
517
518 wxThread::~wxThread()
519 {
520 delete m_internal;
521 }
522
523 // create/start thread
524 // -------------------
525
526 wxThreadError wxThread::Create(
527 unsigned int uStackSize
528 )
529 {
530 if ( !m_internal->Create(this, uStackSize) )
531 return wxTHREAD_NO_RESOURCE;
532
533 return wxTHREAD_NO_ERROR;
534 }
535
536 wxThreadError wxThread::Run()
537 {
538 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
539
540 if ( m_internal->GetState() != STATE_NEW )
541 {
542 // actually, it may be almost any state at all, not only STATE_RUNNING
543 return wxTHREAD_RUNNING;
544 }
545 return Resume();
546 }
547
548 // suspend/resume thread
549 // ---------------------
550
551 wxThreadError wxThread::Pause()
552 {
553 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
554
555 return m_internal->Suspend() ? wxTHREAD_NO_ERROR : wxTHREAD_MISC_ERROR;
556 }
557
558 wxThreadError wxThread::Resume()
559 {
560 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
561
562 return m_internal->Resume() ? wxTHREAD_NO_ERROR : wxTHREAD_MISC_ERROR;
563 }
564
565 // stopping thread
566 // ---------------
567
568 wxThread::ExitCode wxThread::Wait()
569 {
570 // although under Windows we can wait for any thread, it's an error to
571 // wait for a detached one in wxWin API
572 wxCHECK_MSG( !IsDetached(), (ExitCode)-1,
573 _T("can't wait for detached thread") );
574 ExitCode rc = (ExitCode)-1;
575 (void)Delete(&rc);
576 m_internal->Free();
577 return(rc);
578 }
579
580 wxThreadError wxThread::Delete(ExitCode *pRc)
581 {
582 ExitCode rc = 0;
583
584 // Delete() is always safe to call, so consider all possible states
585 if (IsPaused())
586 Resume();
587
588 TID hThread = m_internal->GetHandle();
589
590 if (IsRunning())
591 {
592 if (IsMain())
593 {
594 // set flag for wxIsWaitingForThread()
595 gs_bWaitingForThread = TRUE;
596
597 #if wxUSE_GUI
598 wxBeginBusyCursor();
599 #endif // wxUSE_GUI
600 }
601
602 // ask the thread to terminate
603 {
604 wxCriticalSectionLocker lock(m_critsect);
605 m_internal->Cancel();
606 }
607
608 #if wxUSE_GUI
609 // need a way to finish GUI processing before killing the thread
610 // until then we just exit
611
612 if ((gs_nWaitingForGui > 0) && wxGuiOwnedByMainThread())
613 {
614 wxMutexGuiLeave();
615 }
616 #else // !wxUSE_GUI
617
618 // can't wait for yourself to end under OS/2 so just quit
619
620 #endif // wxUSE_GUI/!wxUSE_GUI
621
622 if ( IsMain() )
623 {
624 gs_bWaitingForThread = FALSE;
625
626 #if wxUSE_GUI
627 wxEndBusyCursor();
628 #endif // wxUSE_GUI
629 }
630 }
631
632 ::DosExit(0, 0);
633 // probably won't get this far, but
634 if (IsDetached())
635 {
636 delete this;
637 }
638
639 if ( pRc )
640 *pRc = rc;
641
642 return rc == (ExitCode)-1 ? wxTHREAD_MISC_ERROR : wxTHREAD_NO_ERROR;
643 }
644
645 wxThreadError wxThread::Kill()
646 {
647 if (!IsRunning())
648 return wxTHREAD_NOT_RUNNING;
649
650 ::DosKillThread(m_internal->GetHandle());
651 m_internal->Free();
652 if (IsDetached())
653 {
654 delete this;
655 }
656 return wxTHREAD_NO_ERROR;
657 }
658
659 void wxThread::Exit(
660 ExitCode pStatus
661 )
662 {
663 m_internal->Free();
664 delete this;
665 ::DosExit(EXIT_THREAD, ULONG(pStatus));
666 wxFAIL_MSG(wxT("Couldn't return from DosExit()!"));
667 }
668
669 void wxThread::SetPriority(
670 unsigned int nPrio
671 )
672 {
673 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
674
675 m_internal->SetPriority(nPrio);
676 }
677
678 unsigned int wxThread::GetPriority() const
679 {
680 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
681
682 return m_internal->GetPriority();
683 }
684
685 unsigned long wxThread::GetId() const
686 {
687 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect); // const_cast
688
689 return (unsigned long)m_internal->GetId();
690 }
691
692 bool wxThread::IsRunning() const
693 {
694 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
695
696 return(m_internal->GetState() == STATE_RUNNING);
697 }
698
699 bool wxThread::IsAlive() const
700 {
701 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
702
703 return (m_internal->GetState() == STATE_RUNNING) ||
704 (m_internal->GetState() == STATE_PAUSED);
705 }
706
707 bool wxThread::IsPaused() const
708 {
709 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
710
711 return (m_internal->GetState() == STATE_PAUSED);
712 }
713
714 bool wxThread::TestDestroy()
715 {
716 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
717
718 return m_internal->GetState() == STATE_CANCELED;
719 }
720
721 // ----------------------------------------------------------------------------
722 // Automatic initialization for thread module
723 // ----------------------------------------------------------------------------
724
725 class wxThreadModule : public wxModule
726 {
727 public:
728 virtual bool OnInit();
729 virtual void OnExit();
730
731 private:
732 DECLARE_DYNAMIC_CLASS(wxThreadModule)
733 };
734
735 IMPLEMENT_DYNAMIC_CLASS(wxThreadModule, wxModule)
736
737 bool wxThreadModule::OnInit()
738 {
739 gs_pCritsectWaitingForGui = new wxCriticalSection();
740
741 gs_pCritsectGui = new wxCriticalSection();
742 gs_pCritsectGui->Enter();
743
744 PTIB ptib;
745 PPIB ppib;
746
747 ::DosGetInfoBlocks(&ptib, &ppib);
748
749 s_ulIdMainThread = ptib->tib_ptib2->tib2_ultid;
750 return TRUE;
751 }
752
753 void wxThreadModule::OnExit()
754 {
755 if (gs_pCritsectGui)
756 {
757 gs_pCritsectGui->Leave();
758 #if (!(defined(__VISAGECPP__) && (__IBMCPP__ < 400 || __IBMC__ < 400 )))
759 delete gs_pCritsectGui;
760 #endif
761 gs_pCritsectGui = NULL;
762 }
763
764 #if (!(defined(__VISAGECPP__) && (__IBMCPP__ < 400 || __IBMC__ < 400 )))
765 wxDELETE(gs_pCritsectWaitingForGui);
766 #endif
767 }
768
769 // ----------------------------------------------------------------------------
770 // Helper functions
771 // ----------------------------------------------------------------------------
772
773 // Does nothing under OS/2 [for now]
774 void WXDLLEXPORT wxWakeUpMainThread()
775 {
776 }
777
778 void WXDLLEXPORT wxMutexGuiLeave()
779 {
780 wxCriticalSectionLocker enter(*gs_pCritsectWaitingForGui);
781
782 if ( wxThread::IsMain() )
783 {
784 gs_bGuiOwnedByMainThread = FALSE;
785 }
786 else
787 {
788 // decrement the number of waiters now
789 wxASSERT_MSG(gs_nWaitingForGui > 0,
790 wxT("calling wxMutexGuiLeave() without entering it first?") );
791
792 gs_nWaitingForGui--;
793
794 wxWakeUpMainThread();
795 }
796
797 gs_pCritsectGui->Leave();
798 }
799
800 void WXDLLEXPORT wxMutexGuiLeaveOrEnter()
801 {
802 wxASSERT_MSG( wxThread::IsMain(),
803 wxT("only main thread may call wxMutexGuiLeaveOrEnter()!") );
804
805 wxCriticalSectionLocker enter(*gs_pCritsectWaitingForGui);
806
807 if (gs_nWaitingForGui == 0)
808 {
809 // no threads are waiting for GUI - so we may acquire the lock without
810 // any danger (but only if we don't already have it)
811 if (!wxGuiOwnedByMainThread())
812 {
813 gs_pCritsectGui->Enter();
814
815 gs_bGuiOwnedByMainThread = TRUE;
816 }
817 //else: already have it, nothing to do
818 }
819 else
820 {
821 // some threads are waiting, release the GUI lock if we have it
822 if (wxGuiOwnedByMainThread())
823 {
824 wxMutexGuiLeave();
825 }
826 //else: some other worker thread is doing GUI
827 }
828 }
829
830 bool WXDLLEXPORT wxGuiOwnedByMainThread()
831 {
832 return gs_bGuiOwnedByMainThread;
833 }
834
835 bool WXDLLEXPORT wxIsWaitingForThread()
836 {
837 return gs_bWaitingForThread;
838 }
839
840 #endif
841 // wxUSE_THREADS