X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/63fa42b3bedaf167004c95cc46339849ee9d7424..69659fd770f615210efac4b4fa741b3ad6223616:/src/msw/thread.cpp diff --git a/src/msw/thread.cpp b/src/msw/thread.cpp index 01378e3940..6b835a9707 100644 --- a/src/msw/thread.cpp +++ b/src/msw/thread.cpp @@ -10,10 +10,6 @@ // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// -#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) - #pragma implementation "thread.h" -#endif - // ---------------------------------------------------------------------------- // headers // ---------------------------------------------------------------------------- @@ -25,20 +21,24 @@ #pragma hdrstop #endif +#if wxUSE_THREADS + +#include "wx/thread.h" + #ifndef WX_PRECOMP #include "wx/intl.h" #include "wx/app.h" + #include "wx/module.h" #endif -#if wxUSE_THREADS - #include "wx/apptrait.h" +#include "wx/scopeguard.h" #include "wx/msw/private.h" #include "wx/msw/missing.h" +#include "wx/msw/seh.h" -#include "wx/module.h" -#include "wx/thread.h" +#include "wx/except.h" // must have this symbol defined to get _beginthread/_endthread declarations #ifndef _MT @@ -112,8 +112,8 @@ static DWORD gs_tlsThisThread = 0xFFFFFFFF; // calling wxMutexGuiEnter() static DWORD gs_idMainThread = 0; -// if it's FALSE, some secondary thread is holding the GUI lock -static bool gs_bGuiOwnedByMainThread = TRUE; +// if it's false, some secondary thread is holding the GUI lock +static bool gs_bGuiOwnedByMainThread = true; // critical section which controls access to all GUI functions: any secondary // thread (i.e. except the main one) must enter this crit section before doing @@ -123,11 +123,16 @@ static wxCriticalSection *gs_critsectGui = NULL; // critical section which protects gs_nWaitingForGui variable static wxCriticalSection *gs_critsectWaitingForGui = NULL; +// critical section which serializes WinThreadStart() and WaitForTerminate() +// (this is a potential bottleneck, we use a single crit sect for all threads +// in the system, but normally time spent inside it should be quite short) +static wxCriticalSection *gs_critsectThreadDelete = NULL; + // number of threads waiting for GUI in wxMutexGuiEnter() static size_t gs_nWaitingForGui = 0; // are we waiting for a thread termination? -static bool gs_waitingForThread = FALSE; +static bool gs_waitingForThread = false; // ============================================================================ // Windows implementation of thread and related classes @@ -173,7 +178,8 @@ public: bool IsOk() const { return m_mutex != NULL; } wxMutexError Lock() { return LockTimeout(INFINITE); } - wxMutexError TryLock() { return LockTimeout(0); } + wxMutexError Lock(unsigned long ms) { return LockTimeout(ms); } + wxMutexError TryLock(); wxMutexError Unlock(); private: @@ -212,6 +218,14 @@ wxMutexInternal::~wxMutexInternal() } } +wxMutexError wxMutexInternal::TryLock() +{ + const wxMutexError rc = LockTimeout(0); + + // we have a special return code for timeout in this case + return rc == wxMUTEX_TIMEOUT ? wxMUTEX_BUSY : rc; +} + wxMutexError wxMutexInternal::LockTimeout(DWORD milliseconds) { DWORD rc = ::WaitForSingleObject(m_mutex, milliseconds); @@ -232,7 +246,7 @@ wxMutexError wxMutexInternal::LockTimeout(DWORD milliseconds) break; case WAIT_TIMEOUT: - return wxMUTEX_BUSY; + return wxMUTEX_TIMEOUT; case WAIT_ABANDONED: // checked for above default: @@ -295,7 +309,7 @@ private: wxSemaphoreInternal::wxSemaphoreInternal(int initialcount, int maxcount) { -#ifndef __WXWINCE__ +#if !defined(_WIN32_WCE) || (_WIN32_WCE >= 300) if ( maxcount == 0 ) { // make it practically infinite @@ -348,16 +362,24 @@ wxSemaError wxSemaphoreInternal::WaitTimeout(unsigned long milliseconds) wxSemaError wxSemaphoreInternal::Post() { -#ifndef __WXWINCE__ +#if !defined(_WIN32_WCE) || (_WIN32_WCE >= 300) if ( !::ReleaseSemaphore(m_semaphore, 1, NULL /* ptr to previous count */) ) -#endif { - wxLogLastError(_T("ReleaseSemaphore")); - - return wxSEMA_MISC_ERROR; + if ( GetLastError() == ERROR_TOO_MANY_POSTS ) + { + return wxSEMA_OVERFLOW; + } + else + { + wxLogLastError(_T("ReleaseSemaphore")); + return wxSEMA_MISC_ERROR; + } } return wxSEMA_NO_ERROR; +#else + return wxSEMA_MISC_ERROR; +#endif } // ---------------------------------------------------------------------------- @@ -370,11 +392,13 @@ wxSemaError wxSemaphoreInternal::Post() class wxThreadInternal { public: - wxThreadInternal() + wxThreadInternal(wxThread *thread) { + m_thread = thread; m_hThread = 0; m_state = STATE_NEW; m_priority = WXTHREAD_DEFAULT_PRIORITY; + m_nRef = 1; } ~wxThreadInternal() @@ -400,9 +424,9 @@ public: // wait for the thread to terminate, either by itself, or by asking it // (politely, this is not Kill()!) to do it - wxThreadError WaitForTerminate(bool shouldCancel, - wxCriticalSection& cs, - wxThread::ExitCode *pRc); + wxThreadError WaitForTerminate(wxCriticalSection& cs, + wxThread::ExitCode *pRc, + wxThread *threadToDelete = NULL); // kill the thread unconditionally wxThreadError Kill(); @@ -424,60 +448,133 @@ public: HANDLE GetHandle() const { return m_hThread; } DWORD GetId() const { return m_tid; } - // thread function + // the thread function forwarding to DoThreadStart static THREAD_RETVAL THREAD_CALLCONV WinThreadStart(void *thread); + // really start the thread (if it's not already dead) + static THREAD_RETVAL DoThreadStart(wxThread *thread); + + // call OnExit() on the thread + static void DoThreadOnExit(wxThread *thread); + + + void KeepAlive() + { + if ( m_thread->IsDetached() ) + ::InterlockedIncrement(&m_nRef); + } + + void LetDie() + { + if ( m_thread->IsDetached() && !::InterlockedDecrement(&m_nRef) ) + delete m_thread; + } + private: + // the thread we're associated with + wxThread *m_thread; + HANDLE m_hThread; // handle of the thread wxThreadState m_state; // state, see wxThreadState enum unsigned int m_priority; // thread priority in "wx" units DWORD m_tid; // thread id + // number of threads which need this thread to remain alive, when the count + // reaches 0 we kill the owning wxThread -- and die ourselves with it + LONG m_nRef; + DECLARE_NO_COPY_CLASS(wxThreadInternal) }; -THREAD_RETVAL THREAD_CALLCONV wxThreadInternal::WinThreadStart(void *param) +// small class which keeps a thread alive during its lifetime +class wxThreadKeepAlive { - THREAD_RETVAL rc; - bool wasCancelled; +public: + wxThreadKeepAlive(wxThreadInternal& thrImpl) : m_thrImpl(thrImpl) + { m_thrImpl.KeepAlive(); } + ~wxThreadKeepAlive() + { m_thrImpl.LetDie(); } - // first of all, check whether we hadn't been cancelled already and don't - // start the user code at all then - wxThread *thread = (wxThread *)param; - if ( thread->m_internal->GetState() == STATE_EXITED ) +private: + wxThreadInternal& m_thrImpl; +}; + +/* static */ +void wxThreadInternal::DoThreadOnExit(wxThread *thread) +{ + wxTRY { - rc = (THREAD_RETVAL)-1; - wasCancelled = TRUE; + thread->OnExit(); } - else // do run thread + wxCATCH_ALL( wxTheApp->OnUnhandledException(); ) +} + +/* static */ +THREAD_RETVAL wxThreadInternal::DoThreadStart(wxThread *thread) +{ + wxON_BLOCK_EXIT1(DoThreadOnExit, thread); + + THREAD_RETVAL rc = (THREAD_RETVAL)-1; + + wxTRY { // store the thread object in the TLS if ( !::TlsSetValue(gs_tlsThisThread, thread) ) { wxLogSysError(_("Can not start thread: error writing TLS.")); - return (DWORD)-1; + return (THREAD_RETVAL)-1; } rc = (THREAD_RETVAL)thread->Entry(); + } + wxCATCH_ALL( wxTheApp->OnUnhandledException(); ) + + return rc; +} + +/* static */ +THREAD_RETVAL THREAD_CALLCONV wxThreadInternal::WinThreadStart(void *param) +{ + THREAD_RETVAL rc = (THREAD_RETVAL)-1; + + wxThread * const thread = (wxThread *)param; + + // each thread has its own SEH translator so install our own a.s.a.p. + DisableAutomaticSETranslator(); + + // first of all, check whether we hadn't been cancelled already and don't + // start the user code at all then + const bool hasExited = thread->m_internal->GetState() == STATE_EXITED; + + // run the thread function itself inside a SEH try/except block + wxSEH_TRY + { + if ( hasExited ) + DoThreadOnExit(thread); + else + rc = DoThreadStart(thread); + } + wxSEH_HANDLE((THREAD_RETVAL)-1) + + // save IsDetached because thread object can be deleted by joinable + // threads after state is changed to STATE_EXITED. + const bool isDetached = thread->IsDetached(); + if ( !hasExited ) + { // enter m_critsect before changing the thread state + // + // NB: can't use wxCriticalSectionLocker here as we use SEH and it's + // incompatible with C++ object dtors thread->m_critsect.Enter(); - wasCancelled = thread->m_internal->GetState() == STATE_CANCELED; thread->m_internal->SetState(STATE_EXITED); thread->m_critsect.Leave(); } - thread->OnExit(); - - // if the thread was cancelled (from Delete()), then its handle is still - // needed there - if ( thread->IsDetached() && !wasCancelled ) - { - // auto delete - delete thread; - } - //else: the joinable threads handle will be closed when Wait() is done + // the thread may delete itself now if it wants, we don't need it any more + if ( isDetached ) + thread->m_internal->LetDie(); return rc; } @@ -486,7 +583,7 @@ void wxThreadInternal::SetPriority(unsigned int priority) { m_priority = priority; - // translate wxWindows priority to the Windows one + // translate wxWidgets priority to the Windows one int win_priority; if (m_priority <= 20) win_priority = THREAD_PRIORITY_LOWEST; @@ -552,7 +649,7 @@ bool wxThreadInternal::Create(wxThread *thread, unsigned int stackSize) { wxLogSysError(_("Can't create thread")); - return FALSE; + return false; } if ( m_priority != WXTHREAD_DEFAULT_PRIORITY ) @@ -560,7 +657,7 @@ bool wxThreadInternal::Create(wxThread *thread, unsigned int stackSize) SetPriority(m_priority); } - return TRUE; + return true; } wxThreadError wxThreadInternal::Kill() @@ -578,46 +675,49 @@ wxThreadError wxThreadInternal::Kill() } wxThreadError -wxThreadInternal::WaitForTerminate(bool shouldCancel, - wxCriticalSection& cs, - wxThread::ExitCode *pRc) +wxThreadInternal::WaitForTerminate(wxCriticalSection& cs, + wxThread::ExitCode *pRc, + wxThread *threadToDelete) { - wxThread::ExitCode rc = 0; + // prevent the thread C++ object from disappearing as long as we are using + // it here + wxThreadKeepAlive keepAlive(*this); + - // Delete() is always safe to call, so consider all possible states + // we may either wait passively for the thread to terminate (when called + // from Wait()) or ask it to terminate (when called from Delete()) + bool shouldDelete = threadToDelete != NULL; - // we might need to resume the thread, but we might also not need to cancel - // it if it doesn't run yet - bool shouldResume = FALSE, - isRunning = FALSE; + wxThread::ExitCode rc = 0; + + // we might need to resume the thread if it's currently stopped + bool shouldResume = false; - // check if the thread already started to run + // as Delete() (which calls us) is always safe to call we need to consider + // all possible states { wxCriticalSectionLocker lock(cs); if ( m_state == STATE_NEW ) { - if ( shouldCancel ) + if ( shouldDelete ) { // WinThreadStart() will see it and terminate immediately, no // need to cancel the thread -- but we still need to resume it // to let it run m_state = STATE_EXITED; - Resume(); // it knows about STATE_EXITED special case - - shouldCancel = FALSE; + // we must call Resume() as the thread hasn't been initially + // resumed yet (and as Resume() it knows about STATE_EXITED + // special case, it won't touch it and WinThreadStart() will + // just exit immediately) + shouldResume = true; + shouldDelete = false; } - - isRunning = TRUE; - - // shouldResume is correctly set to FALSE here - } - else if ( m_state == STATE_EXITED ) - { - return wxTHREAD_NOT_RUNNING; + //else: shouldResume is correctly set to false here, wait until + // someone else runs the thread and it finishes } - else // running (but maybe paused or cancelled) + else // running, paused, cancelled or even exited { shouldResume = m_state == STATE_PAUSED; } @@ -627,125 +727,123 @@ wxThreadInternal::WaitForTerminate(bool shouldCancel, if ( shouldResume ) Resume(); - // is it still running? - if ( isRunning || m_state == STATE_RUNNING ) + // ask the thread to terminate + if ( shouldDelete ) + { + wxCriticalSectionLocker lock(cs); + + Cancel(); + } + + + // now wait for thread to finish + if ( wxThread::IsMain() ) + { + // set flag for wxIsWaitingForThread() + gs_waitingForThread = true; + } + + // we can't just wait for the thread to terminate because it might be + // calling some GUI functions and so it will never terminate before we + // process the Windows messages that result from these functions + // (note that even in console applications we might have to process + // messages if we use wxExecute() or timers or ...) + DWORD result wxDUMMY_INITIALIZE(0); + do { if ( wxThread::IsMain() ) { - // set flag for wxIsWaitingForThread() - gs_waitingForThread = TRUE; + // give the thread we're waiting for chance to do the GUI call + // it might be in + if ( (gs_nWaitingForGui > 0) && wxGuiOwnedByMainThread() ) + { + wxMutexGuiLeave(); + } } - // ask the thread to terminate - if ( shouldCancel ) + wxAppTraits *traits = wxTheApp ? wxTheApp->GetTraits() : NULL; + if ( traits ) { - wxCriticalSectionLocker lock(cs); - - Cancel(); + result = traits->WaitForThread(m_hThread); + } + else // can't wait for the thread + { + // so kill it below + result = 0xFFFFFFFF; } - // we can't just wait for the thread to terminate because it might be - // calling some GUI functions and so it will never terminate before we - // process the Windows messages that result from these functions - // (note that even in console applications we might have to process - // messages if we use wxExecute() or timers or ...) - DWORD result = 0; // suppress warnings from broken compilers - do + switch ( result ) { - if ( wxThread::IsMain() ) - { - // give the thread we're waiting for chance to do the GUI call - // it might be in - if ( (gs_nWaitingForGui > 0) && wxGuiOwnedByMainThread() ) - { - wxMutexGuiLeave(); - } - } + case 0xFFFFFFFF: + // error + wxLogSysError(_("Can not wait for thread termination")); + Kill(); + return wxTHREAD_KILLED; + + case WAIT_OBJECT_0: + // thread we're waiting for terminated + break; - result = ::MsgWaitForMultipleObjects - ( - 1, // number of objects to wait for - &m_hThread, // the objects - FALSE, // don't wait for all objects - INFINITE, // no timeout - QS_ALLINPUT | // return as soon as there are any events - QS_ALLPOSTMESSAGE - ); - - switch ( result ) - { - case 0xFFFFFFFF: - // error - wxLogSysError(_("Can not wait for thread termination")); - Kill(); - return wxTHREAD_KILLED; - - case WAIT_OBJECT_0: - // thread we're waiting for terminated - break; - - case WAIT_OBJECT_0 + 1: - // new message arrived, process it -- but only if we're the - // main thread as we don't support processing messages in - // the other ones - // - // NB: we still must include QS_ALLINPUT even when waiting - // in a secondary thread because if it had created some - // window somehow (possible not even using wxWindows) - // the system might dead lock then - if ( wxThread::IsMain() ) + case WAIT_OBJECT_0 + 1: + // new message arrived, process it -- but only if we're the + // main thread as we don't support processing messages in + // the other ones + // + // NB: we still must include QS_ALLINPUT even when waiting + // in a secondary thread because if it had created some + // window somehow (possible not even using wxWidgets) + // the system might dead lock then + if ( wxThread::IsMain() ) + { + if ( traits && !traits->DoMessageFromThreadWait() ) { - // it looks that sometimes WAIT_OBJECT_0 + 1 is - // returned but there are no messages in the thread - // queue -- prevent DoMessageFromThreadWait() from - // blocking inside ::GetMessage() forever in this case - ::PostMessage(NULL, WM_NULL, 0, 0); - - wxAppTraits *traits = wxTheApp ? wxTheApp->GetTraits() - : NULL; - - if ( traits && !traits->DoMessageFromThreadWait() ) - { - // WM_QUIT received: kill the thread - Kill(); - - return wxTHREAD_KILLED; - } - } - break; + // WM_QUIT received: kill the thread + Kill(); - default: - wxFAIL_MSG(wxT("unexpected result of MsgWaitForMultipleObject")); - } - } while ( result != WAIT_OBJECT_0 ); + return wxTHREAD_KILLED; + } + } + break; - if ( wxThread::IsMain() ) - { - gs_waitingForThread = FALSE; + default: + wxFAIL_MSG(wxT("unexpected result of MsgWaitForMultipleObject")); } + } while ( result != WAIT_OBJECT_0 ); + + if ( wxThread::IsMain() ) + { + gs_waitingForThread = false; } + // although the thread might be already in the EXITED state it might not // have terminated yet and so we are not sure that it has actually // terminated if the "if" above hadn't been taken - do + for ( ;; ) { if ( !::GetExitCodeThread(m_hThread, (LPDWORD)&rc) ) { wxLogLastError(wxT("GetExitCodeThread")); rc = (wxThread::ExitCode)-1; + + break; } - } while ( (DWORD)rc == STILL_ACTIVE ); + + if ( (DWORD)rc != STILL_ACTIVE ) + break; + + // give the other thread some time to terminate, otherwise we may be + // starving it + ::Sleep(1); + } if ( pRc ) *pRc = rc; - // we don't need the thread handle any more + // we don't need the thread handle any more in any case Free(); - wxCriticalSectionLocker lock(cs); - SetState(STATE_EXITED); return rc == (wxThread::ExitCode)-1 ? wxTHREAD_MISC_ERROR : wxTHREAD_NO_ERROR; @@ -758,12 +856,12 @@ bool wxThreadInternal::Suspend() { wxLogSysError(_("Can not suspend thread %x"), m_hThread); - return FALSE; + return false; } m_state = STATE_PAUSED; - return TRUE; + return true; } bool wxThreadInternal::Resume() @@ -773,18 +871,18 @@ bool wxThreadInternal::Resume() { wxLogSysError(_("Can not resume thread %x"), m_hThread); - return FALSE; + return false; } // don't change the state from STATE_EXITED because it's special and means // we are going to terminate without running any user code - if we did it, - // the codei n Delete() wouldn't work + // the code in WaitForTerminate() wouldn't work if ( m_state != STATE_EXITED ) { m_state = STATE_RUNNING; } - return TRUE; + return true; } // static functions @@ -807,7 +905,7 @@ wxThread *wxThread::This() bool wxThread::IsMain() { - return ::GetCurrentThreadId() == gs_idMainThread; + return ::GetCurrentThreadId() == gs_idMainThread || gs_idMainThread == 0; } void wxThread::Yield() @@ -835,9 +933,11 @@ unsigned long wxThread::GetCurrentId() return (unsigned long)::GetCurrentThreadId(); } -bool wxThread::SetConcurrency(size_t level) +bool wxThread::SetConcurrency(size_t WXUNUSED_IN_WINCE(level)) { -#ifndef __WXWINCE__ +#ifdef __WXWINCE__ + return false; +#else wxASSERT_MSG( IsMain(), _T("should only be called from the main thread") ); // ok only for the default one @@ -846,12 +946,12 @@ bool wxThread::SetConcurrency(size_t level) // get system affinity mask first HANDLE hProcess = ::GetCurrentProcess(); - DWORD dwProcMask, dwSysMask; + DWORD_PTR dwProcMask, dwSysMask; if ( ::GetProcessAffinityMask(hProcess, &dwProcMask, &dwSysMask) == 0 ) { wxLogLastError(_T("GetProcessAffinityMask")); - return FALSE; + return false; } // how many CPUs have we got? @@ -874,7 +974,7 @@ bool wxThread::SetConcurrency(size_t level) dwProcMask |= bit; // another process added - if ( !--level ) + if ( --level == 0 ) { // and that's enough break; @@ -890,13 +990,13 @@ bool wxThread::SetConcurrency(size_t level) { wxLogDebug(_T("bad level %u in wxThread::SetConcurrency()"), level); - return FALSE; + return false; } // set it: we can't link to SetProcessAffinityMask() because it doesn't // exist in Win9x, use RT binding instead - typedef BOOL (*SETPROCESSAFFINITYMASK)(HANDLE, DWORD); + typedef BOOL (WINAPI *SETPROCESSAFFINITYMASK)(HANDLE, DWORD_PTR); // can use static var because we're always in the main thread here static SETPROCESSAFFINITYMASK pfnSetProcessAffinityMask = NULL; @@ -918,17 +1018,18 @@ bool wxThread::SetConcurrency(size_t level) if ( !pfnSetProcessAffinityMask ) { // msg given above - do it only once - return FALSE; + return false; } if ( pfnSetProcessAffinityMask(hProcess, dwProcMask) == 0 ) { wxLogLastError(_T("SetProcessAffinityMask")); - return FALSE; + return false; } -#endif - return TRUE; + + return true; +#endif // __WXWINCE__/!__WXWINCE__ } // ctor and dtor @@ -936,7 +1037,7 @@ bool wxThread::SetConcurrency(size_t level) wxThread::wxThread(wxThreadKind kind) { - m_internal = new wxThreadInternal(); + m_internal = new wxThreadInternal(this); m_isDetached = kind == wxTHREAD_DETACHED; } @@ -1002,14 +1103,14 @@ wxThread::ExitCode wxThread::Wait() ExitCode rc = (ExitCode)-1; - (void)m_internal->WaitForTerminate(false, m_critsect, &rc); + (void)m_internal->WaitForTerminate(m_critsect, &rc); return rc; } wxThreadError wxThread::Delete(ExitCode *pRc) { - return m_internal->WaitForTerminate(true, m_critsect, pRc); + return m_internal->WaitForTerminate(m_critsect, pRc, this); } wxThreadError wxThread::Kill() @@ -1137,7 +1238,7 @@ bool wxThreadModule::OnInit() // words, this should never happen wxLogSysError(_("Thread module initialization failed: impossible to allocate index in thread local storage")); - return FALSE; + return false; } // main thread doesn't have associated wxThread object, so store 0 in the @@ -1149,7 +1250,7 @@ bool wxThreadModule::OnInit() wxLogSysError(_("Thread module initialization failed: can not store value in thread local storage")); - return FALSE; + return false; } gs_critsectWaitingForGui = new wxCriticalSection(); @@ -1157,10 +1258,12 @@ bool wxThreadModule::OnInit() gs_critsectGui = new wxCriticalSection(); gs_critsectGui->Enter(); + gs_critsectThreadDelete = new wxCriticalSection; + // no error return for GetCurrentThreadId() gs_idMainThread = ::GetCurrentThreadId(); - return TRUE; + return true; } void wxThreadModule::OnExit() @@ -1170,6 +1273,9 @@ void wxThreadModule::OnExit() wxLogLastError(wxT("TlsFree failed.")); } + delete gs_critsectThreadDelete; + gs_critsectThreadDelete = NULL; + if ( gs_critsectGui ) { gs_critsectGui->Leave(); @@ -1214,7 +1320,7 @@ void WXDLLIMPEXP_BASE wxMutexGuiLeave() if ( wxThread::IsMain() ) { - gs_bGuiOwnedByMainThread = FALSE; + gs_bGuiOwnedByMainThread = false; } else { @@ -1245,7 +1351,7 @@ void WXDLLIMPEXP_BASE wxMutexGuiLeaveOrEnter() { gs_critsectGui->Enter(); - gs_bGuiOwnedByMainThread = TRUE; + gs_bGuiOwnedByMainThread = true; } //else: already have it, nothing to do } @@ -1288,4 +1394,3 @@ bool WXDLLIMPEXP_BASE wxIsWaitingForThread() #include "wx/thrimpl.cpp" #endif // wxUSE_THREADS -