]> git.saurik.com Git - wxWidgets.git/blob - src/msw/thread.cpp
A few weeks of Unicode fixes (my old win95 laptop compiles sloowly,
[wxWidgets.git] / src / msw / thread.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: thread.cpp
3 // Purpose: wxThread Implementation
4 // Author: Original from Wolfram Gloger/Guilhem Lavaux
5 // Modified by: Vadim Zeitlin to make it work :-)
6 // Created: 04/22/98
7 // RCS-ID: $Id$
8 // Copyright: (c) Wolfram Gloger (1996, 1997); Guilhem Lavaux (1998),
9 // Vadim Zeitlin (1999)
10 // Licence: wxWindows licence
11 /////////////////////////////////////////////////////////////////////////////
12
13 #ifdef __GNUG__
14 #pragma implementation "thread.h"
15 #endif
16
17 // ----------------------------------------------------------------------------
18 // headers
19 // ----------------------------------------------------------------------------
20
21 // For compilers that support precompilation, includes "wx.h".
22 #include "wx/wxprec.h"
23
24 #if defined(__BORLANDC__)
25 #pragma hdrstop
26 #endif
27
28 #ifndef WX_PRECOMP
29 #include "wx/wx.h"
30 #endif
31
32 #if wxUSE_THREADS
33
34 #include <stdio.h>
35
36 #include <windows.h>
37
38 #include "wx/module.h"
39 #include "wx/thread.h"
40
41 // the possible states of the thread ("=>" shows all possible transitions from
42 // this state)
43 enum wxThreadState
44 {
45 STATE_NEW, // didn't start execution yet (=> RUNNING)
46 STATE_RUNNING, // thread is running (=> PAUSED, CANCELED)
47 STATE_PAUSED, // thread is temporarily suspended (=> RUNNING)
48 STATE_CANCELED, // thread should terminate a.s.a.p. (=> EXITED)
49 STATE_EXITED // thread is terminating
50 };
51
52 // ----------------------------------------------------------------------------
53 // static variables
54 // ----------------------------------------------------------------------------
55
56 // TLS index of the slot where we store the pointer to the current thread
57 static DWORD s_tlsThisThread = 0xFFFFFFFF;
58
59 // id of the main thread - the one which can call GUI functions without first
60 // calling wxMutexGuiEnter()
61 static DWORD s_idMainThread = 0;
62
63 // if it's FALSE, some secondary thread is holding the GUI lock
64 static bool s_bGuiOwnedByMainThread = TRUE;
65
66 // critical section which controls access to all GUI functions: any secondary
67 // thread (i.e. except the main one) must enter this crit section before doing
68 // any GUI calls
69 static wxCriticalSection *s_critsectGui = NULL;
70
71 // critical section which protects s_nWaitingForGui variable
72 static wxCriticalSection *s_critsectWaitingForGui = NULL;
73
74 // number of threads waiting for GUI in wxMutexGuiEnter()
75 static size_t s_nWaitingForGui = 0;
76
77 // are we waiting for a thread termination?
78 static bool s_waitingForThread = FALSE;
79
80 // ============================================================================
81 // Windows implementation of thread classes
82 // ============================================================================
83
84 // ----------------------------------------------------------------------------
85 // wxMutex implementation
86 // ----------------------------------------------------------------------------
87 class wxMutexInternal
88 {
89 public:
90 HANDLE p_mutex;
91 };
92
93 wxMutex::wxMutex()
94 {
95 p_internal = new wxMutexInternal;
96 p_internal->p_mutex = CreateMutex(NULL, FALSE, NULL);
97 if ( !p_internal->p_mutex )
98 {
99 wxLogSysError(_("Can not create mutex."));
100 }
101
102 m_locked = 0;
103 }
104
105 wxMutex::~wxMutex()
106 {
107 if (m_locked > 0)
108 wxLogDebug(_T("Warning: freeing a locked mutex (%d locks)."), m_locked);
109 CloseHandle(p_internal->p_mutex);
110 }
111
112 wxMutexError wxMutex::Lock()
113 {
114 DWORD ret;
115
116 ret = WaitForSingleObject(p_internal->p_mutex, INFINITE);
117 switch ( ret )
118 {
119 case WAIT_ABANDONED:
120 return wxMUTEX_BUSY;
121
122 case WAIT_OBJECT_0:
123 // ok
124 break;
125
126 case WAIT_FAILED:
127 wxLogSysError(_("Couldn't acquire a mutex lock"));
128 return wxMUTEX_MISC_ERROR;
129
130 case WAIT_TIMEOUT:
131 default:
132 wxFAIL_MSG(_T("impossible return value in wxMutex::Lock"));
133 }
134
135 m_locked++;
136 return wxMUTEX_NO_ERROR;
137 }
138
139 wxMutexError wxMutex::TryLock()
140 {
141 DWORD ret;
142
143 ret = WaitForSingleObject(p_internal->p_mutex, 0);
144 if (ret == WAIT_TIMEOUT || ret == WAIT_ABANDONED)
145 return wxMUTEX_BUSY;
146
147 m_locked++;
148 return wxMUTEX_NO_ERROR;
149 }
150
151 wxMutexError wxMutex::Unlock()
152 {
153 if (m_locked > 0)
154 m_locked--;
155
156 BOOL ret = ReleaseMutex(p_internal->p_mutex);
157 if ( ret == 0 )
158 {
159 wxLogSysError(_("Couldn't release a mutex"));
160 return wxMUTEX_MISC_ERROR;
161 }
162
163 return wxMUTEX_NO_ERROR;
164 }
165
166 // ----------------------------------------------------------------------------
167 // wxCondition implementation
168 // ----------------------------------------------------------------------------
169
170 class wxConditionInternal
171 {
172 public:
173 HANDLE event;
174 int waiters;
175 };
176
177 wxCondition::wxCondition()
178 {
179 p_internal = new wxConditionInternal;
180 p_internal->event = CreateEvent(NULL, FALSE, FALSE, NULL);
181 if ( !p_internal->event )
182 {
183 wxLogSysError(_("Can not create event object."));
184 }
185
186 p_internal->waiters = 0;
187 }
188
189 wxCondition::~wxCondition()
190 {
191 CloseHandle(p_internal->event);
192 }
193
194 void wxCondition::Wait(wxMutex& mutex)
195 {
196 mutex.Unlock();
197 p_internal->waiters++;
198 WaitForSingleObject(p_internal->event, INFINITE);
199 p_internal->waiters--;
200 mutex.Lock();
201 }
202
203 bool wxCondition::Wait(wxMutex& mutex,
204 unsigned long sec,
205 unsigned long nsec)
206 {
207 DWORD ret;
208
209 mutex.Unlock();
210 p_internal->waiters++;
211 ret = WaitForSingleObject(p_internal->event, (sec*1000)+(nsec/1000000));
212 p_internal->waiters--;
213 mutex.Lock();
214
215 return (ret != WAIT_TIMEOUT);
216 }
217
218 void wxCondition::Signal()
219 {
220 SetEvent(p_internal->event);
221 }
222
223 void wxCondition::Broadcast()
224 {
225 int i;
226
227 for (i=0;i<p_internal->waiters;i++)
228 {
229 if ( SetEvent(p_internal->event) == 0 )
230 {
231 wxLogSysError(_("Couldn't change the state of event object."));
232 }
233 }
234 }
235
236 // ----------------------------------------------------------------------------
237 // wxCriticalSection implementation
238 // ----------------------------------------------------------------------------
239
240 class wxCriticalSectionInternal
241 {
242 public:
243 // init the critical section object
244 wxCriticalSectionInternal()
245 { ::InitializeCriticalSection(&m_data); }
246
247 // implicit cast to the associated data
248 operator CRITICAL_SECTION *() { return &m_data; }
249
250 // free the associated ressources
251 ~wxCriticalSectionInternal()
252 { ::DeleteCriticalSection(&m_data); }
253
254 private:
255 CRITICAL_SECTION m_data;
256 };
257
258 wxCriticalSection::wxCriticalSection()
259 {
260 m_critsect = new wxCriticalSectionInternal;
261 }
262
263 wxCriticalSection::~wxCriticalSection()
264 {
265 delete m_critsect;
266 }
267
268 void wxCriticalSection::Enter()
269 {
270 ::EnterCriticalSection(*m_critsect);
271 }
272
273 void wxCriticalSection::Leave()
274 {
275 ::LeaveCriticalSection(*m_critsect);
276 }
277
278 // ----------------------------------------------------------------------------
279 // wxThread implementation
280 // ----------------------------------------------------------------------------
281
282 // wxThreadInternal class
283 // ----------------------
284
285 class wxThreadInternal
286 {
287 public:
288 wxThreadInternal()
289 {
290 m_hThread = 0;
291 m_state = STATE_NEW;
292 m_priority = WXTHREAD_DEFAULT_PRIORITY;
293 }
294
295 // create a new (suspended) thread (for the given thread object)
296 bool Create(wxThread *thread);
297
298 // suspend/resume/terminate
299 bool Suspend();
300 bool Resume();
301 void Cancel() { m_state = STATE_CANCELED; }
302
303 // thread state
304 void SetState(wxThreadState state) { m_state = state; }
305 wxThreadState GetState() const { return m_state; }
306
307 // thread priority
308 void SetPriority(unsigned int priority) { m_priority = priority; }
309 unsigned int GetPriority() const { return m_priority; }
310
311 // thread handle and id
312 HANDLE GetHandle() const { return m_hThread; }
313 DWORD GetId() const { return m_tid; }
314
315 // thread function
316 static DWORD WinThreadStart(wxThread *thread);
317
318 private:
319 HANDLE m_hThread; // handle of the thread
320 wxThreadState m_state; // state, see wxThreadState enum
321 unsigned int m_priority; // thread priority in "wx" units
322 DWORD m_tid; // thread id
323 };
324
325 DWORD wxThreadInternal::WinThreadStart(wxThread *thread)
326 {
327 // store the thread object in the TLS
328 if ( !::TlsSetValue(s_tlsThisThread, thread) )
329 {
330 wxLogSysError(_("Can not start thread: error writing TLS."));
331
332 return (DWORD)-1;
333 }
334
335 DWORD ret = (DWORD)thread->Entry();
336 thread->p_internal->SetState(STATE_EXITED);
337 thread->OnExit();
338
339 delete thread;
340
341 return ret;
342 }
343
344 bool wxThreadInternal::Create(wxThread *thread)
345 {
346 m_hThread = ::CreateThread
347 (
348 NULL, // default security
349 0, // default stack size
350 (LPTHREAD_START_ROUTINE) // thread entry point
351 wxThreadInternal::WinThreadStart, //
352 (LPVOID)thread, // parameter
353 CREATE_SUSPENDED, // flags
354 &m_tid // [out] thread id
355 );
356
357 if ( m_hThread == NULL )
358 {
359 wxLogSysError(_("Can't create thread"));
360
361 return FALSE;
362 }
363
364 // translate wxWindows priority to the Windows one
365 int win_priority;
366 if (m_priority <= 20)
367 win_priority = THREAD_PRIORITY_LOWEST;
368 else if (m_priority <= 40)
369 win_priority = THREAD_PRIORITY_BELOW_NORMAL;
370 else if (m_priority <= 60)
371 win_priority = THREAD_PRIORITY_NORMAL;
372 else if (m_priority <= 80)
373 win_priority = THREAD_PRIORITY_ABOVE_NORMAL;
374 else if (m_priority <= 100)
375 win_priority = THREAD_PRIORITY_HIGHEST;
376 else
377 {
378 wxFAIL_MSG(_T("invalid value of thread priority parameter"));
379 win_priority = THREAD_PRIORITY_NORMAL;
380 }
381
382 if ( ::SetThreadPriority(m_hThread, win_priority) == 0 )
383 {
384 wxLogSysError(_("Can't set thread priority"));
385 }
386
387 return TRUE;
388 }
389
390 bool wxThreadInternal::Suspend()
391 {
392 DWORD nSuspendCount = ::SuspendThread(m_hThread);
393 if ( nSuspendCount == (DWORD)-1 )
394 {
395 wxLogSysError(_("Can not suspend thread %x"), m_hThread);
396
397 return FALSE;
398 }
399
400 m_state = STATE_PAUSED;
401
402 return TRUE;
403 }
404
405 bool wxThreadInternal::Resume()
406 {
407 DWORD nSuspendCount = ::ResumeThread(m_hThread);
408 if ( nSuspendCount == (DWORD)-1 )
409 {
410 wxLogSysError(_("Can not resume thread %x"), m_hThread);
411
412 return FALSE;
413 }
414
415 m_state = STATE_RUNNING;
416
417 return TRUE;
418 }
419
420 // static functions
421 // ----------------
422
423 wxThread *wxThread::This()
424 {
425 wxThread *thread = (wxThread *)::TlsGetValue(s_tlsThisThread);
426
427 // be careful, 0 may be a valid return value as well
428 if ( !thread && (::GetLastError() != NO_ERROR) )
429 {
430 wxLogSysError(_("Couldn't get the current thread pointer"));
431
432 // return NULL...
433 }
434
435 return thread;
436 }
437
438 bool wxThread::IsMain()
439 {
440 return ::GetCurrentThreadId() == s_idMainThread;
441 }
442
443 #ifdef Yield
444 #undef Yield
445 #endif
446
447 void wxThread::Yield()
448 {
449 // 0 argument to Sleep() is special
450 ::Sleep(0);
451 }
452
453 void wxThread::Sleep(unsigned long milliseconds)
454 {
455 ::Sleep(milliseconds);
456 }
457
458 // create/start thread
459 // -------------------
460
461 wxThreadError wxThread::Create()
462 {
463 if ( !p_internal->Create(this) )
464 return wxTHREAD_NO_RESOURCE;
465
466 return wxTHREAD_NO_ERROR;
467 }
468
469 wxThreadError wxThread::Run()
470 {
471 wxCriticalSectionLocker lock(m_critsect);
472
473 if ( p_internal->GetState() != STATE_NEW )
474 {
475 // actually, it may be almost any state at all, not only STATE_RUNNING
476 return wxTHREAD_RUNNING;
477 }
478
479 return Resume();
480 }
481
482 // suspend/resume thread
483 // ---------------------
484
485 wxThreadError wxThread::Pause()
486 {
487 wxCriticalSectionLocker lock(m_critsect);
488
489 return p_internal->Suspend() ? wxTHREAD_NO_ERROR : wxTHREAD_MISC_ERROR;
490 }
491
492 wxThreadError wxThread::Resume()
493 {
494 wxCriticalSectionLocker lock(m_critsect);
495
496 return p_internal->Resume() ? wxTHREAD_NO_ERROR : wxTHREAD_MISC_ERROR;
497 }
498
499 // stopping thread
500 // ---------------
501
502 wxThread::ExitCode wxThread::Delete()
503 {
504 ExitCode rc = 0;
505
506 // Delete() is always safe to call, so consider all possible states
507 if ( IsPaused() )
508 Resume();
509
510 if ( IsRunning() )
511 {
512 if ( IsMain() )
513 {
514 // set flag for wxIsWaitingForThread()
515 s_waitingForThread = TRUE;
516
517 wxBeginBusyCursor();
518 }
519
520 HANDLE hThread;
521 {
522 wxCriticalSectionLocker lock(m_critsect);
523
524 p_internal->Cancel();
525 hThread = p_internal->GetHandle();
526 }
527
528 // we can't just wait for the thread to terminate because it might be
529 // calling some GUI functions and so it will never terminate before we
530 // process the Windows messages that result from these functions
531 DWORD result;
532 do
533 {
534 result = ::MsgWaitForMultipleObjects
535 (
536 1, // number of objects to wait for
537 &hThread, // the objects
538 FALSE, // don't wait for all objects
539 INFINITE, // no timeout
540 QS_ALLEVENTS // return as soon as there are any events
541 );
542
543 switch ( result )
544 {
545 case 0xFFFFFFFF:
546 // error
547 wxLogSysError(_("Can not wait for thread termination"));
548 Kill();
549 return (ExitCode)-1;
550
551 case WAIT_OBJECT_0:
552 // thread we're waiting for terminated
553 break;
554
555 case WAIT_OBJECT_0 + 1:
556 // new message arrived, process it
557 if ( !wxTheApp->DoMessage() )
558 {
559 // WM_QUIT received: kill the thread
560 Kill();
561
562 return (ExitCode)-1;
563 }
564
565 if ( IsMain() )
566 {
567 // give the thread we're waiting for chance to exit
568 // from the GUI call it might have been in
569 if ( (s_nWaitingForGui > 0) && wxGuiOwnedByMainThread() )
570 {
571 wxMutexGuiLeave();
572 }
573 }
574
575 break;
576
577 default:
578 wxFAIL_MSG(_T("unexpected result of MsgWaitForMultipleObject"));
579 }
580 } while ( result != WAIT_OBJECT_0 );
581
582 if ( IsMain() )
583 {
584 s_waitingForThread = FALSE;
585
586 wxEndBusyCursor();
587 }
588
589 if ( !::GetExitCodeThread(hThread, (LPDWORD)&rc) )
590 {
591 wxLogLastError("GetExitCodeThread");
592
593 rc = (ExitCode)-1;
594 }
595
596 wxASSERT_MSG( (LPVOID)rc != (LPVOID)STILL_ACTIVE,
597 _T("thread must be already terminated.") );
598
599 ::CloseHandle(hThread);
600 }
601
602 return rc;
603 }
604
605 wxThreadError wxThread::Kill()
606 {
607 if ( !IsRunning() )
608 return wxTHREAD_NOT_RUNNING;
609
610 if ( !::TerminateThread(p_internal->GetHandle(), (DWORD)-1) )
611 {
612 wxLogSysError(_("Couldn't terminate thread"));
613
614 return wxTHREAD_MISC_ERROR;
615 }
616
617 delete this;
618
619 return wxTHREAD_NO_ERROR;
620 }
621
622 void wxThread::Exit(void *status)
623 {
624 delete this;
625
626 ::ExitThread((DWORD)status);
627
628 wxFAIL_MSG(_T("Couldn't return from ExitThread()!"));
629 }
630
631 void wxThread::SetPriority(unsigned int prio)
632 {
633 wxCriticalSectionLocker lock(m_critsect);
634
635 p_internal->SetPriority(prio);
636 }
637
638 unsigned int wxThread::GetPriority() const
639 {
640 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
641
642 return p_internal->GetPriority();
643 }
644
645 unsigned long wxThread::GetID() const
646 {
647 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
648
649 return (unsigned long)p_internal->GetId();
650 }
651
652 bool wxThread::IsRunning() const
653 {
654 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
655
656 return p_internal->GetState() == STATE_RUNNING;
657 }
658
659 bool wxThread::IsAlive() const
660 {
661 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
662
663 return (p_internal->GetState() == STATE_RUNNING) ||
664 (p_internal->GetState() == STATE_PAUSED);
665 }
666
667 bool wxThread::IsPaused() const
668 {
669 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
670
671 return (p_internal->GetState() == STATE_PAUSED);
672 }
673
674 bool wxThread::TestDestroy()
675 {
676 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
677
678 return p_internal->GetState() == STATE_CANCELED;
679 }
680
681 wxThread::wxThread()
682 {
683 p_internal = new wxThreadInternal();
684 }
685
686 wxThread::~wxThread()
687 {
688 delete p_internal;
689 }
690
691 // ----------------------------------------------------------------------------
692 // Automatic initialization for thread module
693 // ----------------------------------------------------------------------------
694
695 class wxThreadModule : public wxModule
696 {
697 public:
698 virtual bool OnInit();
699 virtual void OnExit();
700
701 private:
702 DECLARE_DYNAMIC_CLASS(wxThreadModule)
703 };
704
705 IMPLEMENT_DYNAMIC_CLASS(wxThreadModule, wxModule)
706
707 bool wxThreadModule::OnInit()
708 {
709 // allocate TLS index for storing the pointer to the current thread
710 s_tlsThisThread = ::TlsAlloc();
711 if ( s_tlsThisThread == 0xFFFFFFFF )
712 {
713 // in normal circumstances it will only happen if all other
714 // TLS_MINIMUM_AVAILABLE (>= 64) indices are already taken - in other
715 // words, this should never happen
716 wxLogSysError(_("Thread module initialization failed: "
717 "impossible to allocate index in thread "
718 "local storage"));
719
720 return FALSE;
721 }
722
723 // main thread doesn't have associated wxThread object, so store 0 in the
724 // TLS instead
725 if ( !::TlsSetValue(s_tlsThisThread, (LPVOID)0) )
726 {
727 ::TlsFree(s_tlsThisThread);
728 s_tlsThisThread = 0xFFFFFFFF;
729
730 wxLogSysError(_("Thread module initialization failed: "
731 "can not store value in thread local storage"));
732
733 return FALSE;
734 }
735
736 s_critsectWaitingForGui = new wxCriticalSection();
737
738 s_critsectGui = new wxCriticalSection();
739 s_critsectGui->Enter();
740
741 // no error return for GetCurrentThreadId()
742 s_idMainThread = ::GetCurrentThreadId();
743
744 return TRUE;
745 }
746
747 void wxThreadModule::OnExit()
748 {
749 if ( !::TlsFree(s_tlsThisThread) )
750 {
751 wxLogLastError("TlsFree failed.");
752 }
753
754 if ( s_critsectGui )
755 {
756 s_critsectGui->Leave();
757 delete s_critsectGui;
758 s_critsectGui = NULL;
759 }
760
761 wxDELETE(s_critsectWaitingForGui);
762 }
763
764 // ----------------------------------------------------------------------------
765 // under Windows, these functions are implemented usign a critical section and
766 // not a mutex, so the names are a bit confusing
767 // ----------------------------------------------------------------------------
768
769 void WXDLLEXPORT wxMutexGuiEnter()
770 {
771 // this would dead lock everything...
772 wxASSERT_MSG( !wxThread::IsMain(),
773 _T("main thread doesn't want to block in wxMutexGuiEnter()!") );
774
775 // the order in which we enter the critical sections here is crucial!!
776
777 // set the flag telling to the main thread that we want to do some GUI
778 {
779 wxCriticalSectionLocker enter(*s_critsectWaitingForGui);
780
781 s_nWaitingForGui++;
782 }
783
784 wxWakeUpMainThread();
785
786 // now we may block here because the main thread will soon let us in
787 // (during the next iteration of OnIdle())
788 s_critsectGui->Enter();
789 }
790
791 void WXDLLEXPORT wxMutexGuiLeave()
792 {
793 wxCriticalSectionLocker enter(*s_critsectWaitingForGui);
794
795 if ( wxThread::IsMain() )
796 {
797 s_bGuiOwnedByMainThread = FALSE;
798 }
799 else
800 {
801 // decrement the number of waiters now
802 wxASSERT_MSG( s_nWaitingForGui > 0,
803 _T("calling wxMutexGuiLeave() without entering it first?") );
804
805 s_nWaitingForGui--;
806
807 wxWakeUpMainThread();
808 }
809
810 s_critsectGui->Leave();
811 }
812
813 void WXDLLEXPORT wxMutexGuiLeaveOrEnter()
814 {
815 wxASSERT_MSG( wxThread::IsMain(),
816 _T("only main thread may call wxMutexGuiLeaveOrEnter()!") );
817
818 wxCriticalSectionLocker enter(*s_critsectWaitingForGui);
819
820 if ( s_nWaitingForGui == 0 )
821 {
822 // no threads are waiting for GUI - so we may acquire the lock without
823 // any danger (but only if we don't already have it)
824 if ( !wxGuiOwnedByMainThread() )
825 {
826 s_critsectGui->Enter();
827
828 s_bGuiOwnedByMainThread = TRUE;
829 }
830 //else: already have it, nothing to do
831 }
832 else
833 {
834 // some threads are waiting, release the GUI lock if we have it
835 if ( wxGuiOwnedByMainThread() )
836 {
837 wxMutexGuiLeave();
838 }
839 //else: some other worker thread is doing GUI
840 }
841 }
842
843 bool WXDLLEXPORT wxGuiOwnedByMainThread()
844 {
845 return s_bGuiOwnedByMainThread;
846 }
847
848 // wake up the main thread if it's in ::GetMessage()
849 void WXDLLEXPORT wxWakeUpMainThread()
850 {
851 // sending any message would do - hopefully WM_NULL is harmless enough
852 if ( !::PostThreadMessage(s_idMainThread, WM_NULL, 0, 0) )
853 {
854 // should never happen
855 wxLogLastError("PostThreadMessage(WM_NULL)");
856 }
857 }
858
859 bool WXDLLEXPORT wxIsWaitingForThread()
860 {
861 return s_waitingForThread;
862 }
863
864 #endif // wxUSE_THREADS