]> git.saurik.com Git - wxWidgets.git/blob - src/msw/thread.cpp
SetCursor() works now
[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("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("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 = NULL;
261 }
262
263 wxCriticalSection::~wxCriticalSection()
264 {
265 wxASSERT_MSG( !m_critsect, "Forgot to Leave() critical section" );
266 }
267
268 void wxCriticalSection::Enter()
269 {
270 m_critsect = new wxCriticalSectionInternal;
271
272 ::EnterCriticalSection(*m_critsect);
273 }
274
275 void wxCriticalSection::Leave()
276 {
277 wxCHECK_RET( m_critsect, "Leave() without matching Enter()" );
278
279 ::LeaveCriticalSection(*m_critsect);
280
281 delete m_critsect;
282 m_critsect = NULL;
283 }
284
285 // ----------------------------------------------------------------------------
286 // wxThread implementation
287 // ----------------------------------------------------------------------------
288
289 // wxThreadInternal class
290 // ----------------------
291
292 class wxThreadInternal
293 {
294 public:
295 wxThreadInternal()
296 {
297 m_hThread = 0;
298 m_state = STATE_NEW;
299 m_priority = WXTHREAD_DEFAULT_PRIORITY;
300 }
301
302 // create a new (suspended) thread (for the given thread object)
303 bool Create(wxThread *thread);
304
305 // suspend/resume/terminate
306 bool Suspend();
307 bool Resume();
308 void Cancel() { m_state = STATE_CANCELED; }
309
310 // thread state
311 void SetState(wxThreadState state) { m_state = state; }
312 wxThreadState GetState() const { return m_state; }
313
314 // thread priority
315 void SetPriority(unsigned int priority) { m_priority = priority; }
316 unsigned int GetPriority() const { return m_priority; }
317
318 // thread handle and id
319 HANDLE GetHandle() const { return m_hThread; }
320 DWORD GetId() const { return m_tid; }
321
322 // thread function
323 static DWORD WinThreadStart(wxThread *thread);
324
325 private:
326 HANDLE m_hThread; // handle of the thread
327 wxThreadState m_state; // state, see wxThreadState enum
328 unsigned int m_priority; // thread priority in "wx" units
329 DWORD m_tid; // thread id
330 };
331
332 DWORD wxThreadInternal::WinThreadStart(wxThread *thread)
333 {
334 // store the thread object in the TLS
335 if ( !::TlsSetValue(s_tlsThisThread, thread) )
336 {
337 wxLogSysError(_("Can not start thread: error writing TLS."));
338
339 return (DWORD)-1;
340 }
341
342 DWORD ret = (DWORD)thread->Entry();
343 thread->p_internal->SetState(STATE_EXITED);
344 thread->OnExit();
345
346 delete thread;
347
348 return ret;
349 }
350
351 bool wxThreadInternal::Create(wxThread *thread)
352 {
353 m_hThread = ::CreateThread
354 (
355 NULL, // default security
356 0, // default stack size
357 (LPTHREAD_START_ROUTINE) // thread entry point
358 wxThreadInternal::WinThreadStart, //
359 (LPVOID)thread, // parameter
360 CREATE_SUSPENDED, // flags
361 &m_tid // [out] thread id
362 );
363
364 if ( m_hThread == NULL )
365 {
366 wxLogSysError(_("Can't create thread"));
367
368 return FALSE;
369 }
370
371 // translate wxWindows priority to the Windows one
372 int win_priority;
373 if (m_priority <= 20)
374 win_priority = THREAD_PRIORITY_LOWEST;
375 else if (m_priority <= 40)
376 win_priority = THREAD_PRIORITY_BELOW_NORMAL;
377 else if (m_priority <= 60)
378 win_priority = THREAD_PRIORITY_NORMAL;
379 else if (m_priority <= 80)
380 win_priority = THREAD_PRIORITY_ABOVE_NORMAL;
381 else if (m_priority <= 100)
382 win_priority = THREAD_PRIORITY_HIGHEST;
383 else
384 {
385 wxFAIL_MSG("invalid value of thread priority parameter");
386 win_priority = THREAD_PRIORITY_NORMAL;
387 }
388
389 if ( ::SetThreadPriority(m_hThread, win_priority) == 0 )
390 {
391 wxLogSysError(_("Can't set thread priority"));
392 }
393
394 return TRUE;
395 }
396
397 bool wxThreadInternal::Suspend()
398 {
399 DWORD nSuspendCount = ::SuspendThread(m_hThread);
400 if ( nSuspendCount == (DWORD)-1 )
401 {
402 wxLogSysError(_("Can not suspend thread %x"), m_hThread);
403
404 return FALSE;
405 }
406
407 m_state = STATE_PAUSED;
408
409 return TRUE;
410 }
411
412 bool wxThreadInternal::Resume()
413 {
414 DWORD nSuspendCount = ::ResumeThread(m_hThread);
415 if ( nSuspendCount == (DWORD)-1 )
416 {
417 wxLogSysError(_("Can not resume thread %x"), m_hThread);
418
419 return FALSE;
420 }
421
422 m_state = STATE_RUNNING;
423
424 return TRUE;
425 }
426
427 // static functions
428 // ----------------
429
430 wxThread *wxThread::This()
431 {
432 wxThread *thread = (wxThread *)::TlsGetValue(s_tlsThisThread);
433
434 // be careful, 0 may be a valid return value as well
435 if ( !thread && (::GetLastError() != NO_ERROR) )
436 {
437 wxLogSysError(_("Couldn't get the current thread pointer"));
438
439 // return NULL...
440 }
441
442 return thread;
443 }
444
445 bool wxThread::IsMain()
446 {
447 return ::GetCurrentThreadId() == s_idMainThread;
448 }
449
450 void wxThread::Yield()
451 {
452 // 0 argument to Sleep() is special
453 ::Sleep(0);
454 }
455
456 void wxThread::Sleep(unsigned long milliseconds)
457 {
458 ::Sleep(milliseconds);
459 }
460
461 // create/start thread
462 // -------------------
463
464 wxThreadError wxThread::Create()
465 {
466 if ( !p_internal->Create(this) )
467 return wxTHREAD_NO_RESOURCE;
468
469 return wxTHREAD_NO_ERROR;
470 }
471
472 wxThreadError wxThread::Run()
473 {
474 wxCriticalSectionLocker lock(m_critsect);
475
476 if ( p_internal->GetState() != STATE_NEW )
477 {
478 // actually, it may be almost any state at all, not only STATE_RUNNING
479 return wxTHREAD_RUNNING;
480 }
481
482 return Resume();
483 }
484
485 // suspend/resume thread
486 // ---------------------
487
488 wxThreadError wxThread::Pause()
489 {
490 wxCriticalSectionLocker lock(m_critsect);
491
492 return p_internal->Suspend() ? wxTHREAD_NO_ERROR : wxTHREAD_MISC_ERROR;
493 }
494
495 wxThreadError wxThread::Resume()
496 {
497 wxCriticalSectionLocker lock(m_critsect);
498
499 return p_internal->Resume() ? wxTHREAD_NO_ERROR : wxTHREAD_MISC_ERROR;
500 }
501
502 // stopping thread
503 // ---------------
504
505 wxThread::ExitCode wxThread::Delete()
506 {
507 ExitCode rc = 0;
508
509 // Delete() is always safe to call, so consider all possible states
510 if ( IsPaused() )
511 Resume();
512
513 if ( IsRunning() )
514 {
515 if ( IsMain() )
516 {
517 // set flag for wxIsWaitingForThread()
518 s_waitingForThread = TRUE;
519
520 wxBeginBusyCursor();
521 }
522
523 HANDLE hThread;
524 {
525 wxCriticalSectionLocker lock(m_critsect);
526
527 p_internal->Cancel();
528 hThread = p_internal->GetHandle();
529 }
530
531 // we can't just wait for the thread to terminate because it might be
532 // calling some GUI functions and so it will never terminate before we
533 // process the Windows messages that result from these functions
534 DWORD result;
535 do
536 {
537 result = ::MsgWaitForMultipleObjects
538 (
539 1, // number of objects to wait for
540 &hThread, // the objects
541 FALSE, // don't wait for all objects
542 INFINITE, // no timeout
543 QS_ALLEVENTS // return as soon as there are any events
544 );
545
546 switch ( result )
547 {
548 case 0xFFFFFFFF:
549 // error
550 wxLogSysError(_("Can not wait for thread termination"));
551 Kill();
552 return (ExitCode)-1;
553
554 case WAIT_OBJECT_0:
555 // thread we're waiting for terminated
556 break;
557
558 case WAIT_OBJECT_0 + 1:
559 // new message arrived, process it
560 if ( !wxTheApp->DoMessage() )
561 {
562 // WM_QUIT received: kill the thread
563 Kill();
564
565 return (ExitCode)-1;
566 }
567
568 if ( IsMain() )
569 {
570 // give the thread we're waiting for chance to exit
571 // from the GUI call it might have been in
572 if ( (s_nWaitingForGui > 0) && wxGuiOwnedByMainThread() )
573 {
574 wxMutexGuiLeave();
575 }
576 }
577
578 break;
579
580 default:
581 wxFAIL_MSG("unexpected result of MsgWaitForMultipleObject");
582 }
583 } while ( result != WAIT_OBJECT_0 );
584
585 if ( IsMain() )
586 {
587 s_waitingForThread = FALSE;
588
589 wxEndBusyCursor();
590 }
591
592 if ( !::GetExitCodeThread(hThread, (LPDWORD)&rc) )
593 {
594 wxLogLastError("GetExitCodeThread");
595
596 rc = (ExitCode)-1;
597 }
598
599 wxASSERT_MSG( (LPVOID)rc != (LPVOID)STILL_ACTIVE,
600 "thread must be already terminated." );
601
602 ::CloseHandle(hThread);
603 }
604
605 return rc;
606 }
607
608 wxThreadError wxThread::Kill()
609 {
610 if ( !IsRunning() )
611 return wxTHREAD_NOT_RUNNING;
612
613 if ( !::TerminateThread(p_internal->GetHandle(), (DWORD)-1) )
614 {
615 wxLogSysError(_("Couldn't terminate thread"));
616
617 return wxTHREAD_MISC_ERROR;
618 }
619
620 delete this;
621
622 return wxTHREAD_NO_ERROR;
623 }
624
625 void wxThread::Exit(void *status)
626 {
627 delete this;
628
629 ::ExitThread((DWORD)status);
630
631 wxFAIL_MSG("Couldn't return from ExitThread()!");
632 }
633
634 void wxThread::SetPriority(unsigned int prio)
635 {
636 wxCriticalSectionLocker lock(m_critsect);
637
638 p_internal->SetPriority(prio);
639 }
640
641 unsigned int wxThread::GetPriority() const
642 {
643 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
644
645 return p_internal->GetPriority();
646 }
647
648 unsigned long wxThread::GetID() const
649 {
650 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
651
652 return (unsigned long)p_internal->GetId();
653 }
654
655 bool wxThread::IsRunning() const
656 {
657 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
658
659 return p_internal->GetState() == STATE_RUNNING;
660 }
661
662 bool wxThread::IsAlive() const
663 {
664 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
665
666 return (p_internal->GetState() == STATE_RUNNING) ||
667 (p_internal->GetState() == STATE_PAUSED);
668 }
669
670 bool wxThread::TestDestroy()
671 {
672 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
673
674 return p_internal->GetState() == STATE_CANCELED;
675 }
676
677 wxThread::wxThread()
678 {
679 p_internal = new wxThreadInternal();
680 }
681
682 wxThread::~wxThread()
683 {
684 delete p_internal;
685 }
686
687 // ----------------------------------------------------------------------------
688 // Automatic initialization for thread module
689 // ----------------------------------------------------------------------------
690
691 class wxThreadModule : public wxModule
692 {
693 public:
694 virtual bool OnInit();
695 virtual void OnExit();
696
697 private:
698 DECLARE_DYNAMIC_CLASS(wxThreadModule)
699 };
700
701 IMPLEMENT_DYNAMIC_CLASS(wxThreadModule, wxModule)
702
703 bool wxThreadModule::OnInit()
704 {
705 // allocate TLS index for storing the pointer to the current thread
706 s_tlsThisThread = ::TlsAlloc();
707 if ( s_tlsThisThread == 0xFFFFFFFF )
708 {
709 // in normal circumstances it will only happen if all other
710 // TLS_MINIMUM_AVAILABLE (>= 64) indices are already taken - in other
711 // words, this should never happen
712 wxLogSysError(_("Thread module initialization failed: "
713 "impossible to allocate index in thread "
714 "local storage"));
715
716 return FALSE;
717 }
718
719 // main thread doesn't have associated wxThread object, so store 0 in the
720 // TLS instead
721 if ( !::TlsSetValue(s_tlsThisThread, (LPVOID)0) )
722 {
723 ::TlsFree(s_tlsThisThread);
724 s_tlsThisThread = 0xFFFFFFFF;
725
726 wxLogSysError(_("Thread module initialization failed: "
727 "can not store value in thread local storage"));
728
729 return FALSE;
730 }
731
732 s_critsectWaitingForGui = new wxCriticalSection();
733
734 s_critsectGui = new wxCriticalSection();
735 s_critsectGui->Enter();
736
737 // no error return for GetCurrentThreadId()
738 s_idMainThread = ::GetCurrentThreadId();
739
740 return TRUE;
741 }
742
743 void wxThreadModule::OnExit()
744 {
745 if ( !::TlsFree(s_tlsThisThread) )
746 {
747 wxLogLastError("TlsFree failed.");
748 }
749
750 if ( s_critsectGui )
751 {
752 s_critsectGui->Leave();
753 delete s_critsectGui;
754 s_critsectGui = NULL;
755 }
756
757 wxDELETE(s_critsectWaitingForGui);
758 }
759
760 // ----------------------------------------------------------------------------
761 // under Windows, these functions are implemented usign a critical section and
762 // not a mutex, so the names are a bit confusing
763 // ----------------------------------------------------------------------------
764
765 void WXDLLEXPORT wxMutexGuiEnter()
766 {
767 // this would dead lock everything...
768 wxASSERT_MSG( !wxThread::IsMain(),
769 "main thread doesn't want to block in wxMutexGuiEnter()!" );
770
771 // the order in which we enter the critical sections here is crucial!!
772
773 // set the flag telling to the main thread that we want to do some GUI
774 {
775 wxCriticalSectionLocker enter(*s_critsectWaitingForGui);
776
777 s_nWaitingForGui++;
778 }
779
780 wxWakeUpMainThread();
781
782 // now we may block here because the main thread will soon let us in
783 // (during the next iteration of OnIdle())
784 s_critsectGui->Enter();
785 }
786
787 void WXDLLEXPORT wxMutexGuiLeave()
788 {
789 wxCriticalSectionLocker enter(*s_critsectWaitingForGui);
790
791 if ( wxThread::IsMain() )
792 {
793 s_bGuiOwnedByMainThread = FALSE;
794 }
795 else
796 {
797 // decrement the number of waiters now
798 wxASSERT_MSG( s_nWaitingForGui > 0,
799 "calling wxMutexGuiLeave() without entering it first?" );
800
801 s_nWaitingForGui--;
802
803 wxWakeUpMainThread();
804 }
805
806 s_critsectGui->Leave();
807 }
808
809 void WXDLLEXPORT wxMutexGuiLeaveOrEnter()
810 {
811 wxASSERT_MSG( wxThread::IsMain(),
812 "only main thread may call wxMutexGuiLeaveOrEnter()!" );
813
814 wxCriticalSectionLocker enter(*s_critsectWaitingForGui);
815
816 if ( s_nWaitingForGui == 0 )
817 {
818 // no threads are waiting for GUI - so we may acquire the lock without
819 // any danger (but only if we don't already have it)
820 if ( !wxGuiOwnedByMainThread() )
821 {
822 s_critsectGui->Enter();
823
824 s_bGuiOwnedByMainThread = TRUE;
825 }
826 //else: already have it, nothing to do
827 }
828 else
829 {
830 // some threads are waiting, release the GUI lock if we have it
831 if ( wxGuiOwnedByMainThread() )
832 {
833 wxMutexGuiLeave();
834 }
835 //else: some other worker thread is doing GUI
836 }
837 }
838
839 bool WXDLLEXPORT wxGuiOwnedByMainThread()
840 {
841 return s_bGuiOwnedByMainThread;
842 }
843
844 // wake up the main thread if it's in ::GetMessage()
845 void WXDLLEXPORT wxWakeUpMainThread()
846 {
847 // sending any message would do - hopefully WM_NULL is harmless enough
848 if ( !::PostThreadMessage(s_idMainThread, WM_NULL, 0, 0) )
849 {
850 // should never happen
851 wxLogLastError("PostThreadMessage(WM_NULL)");
852 }
853 }
854
855 bool WXDLLEXPORT wxIsWaitingForThread()
856 {
857 return s_waitingForThread;
858 }
859
860 #endif // wxUSE_THREADS