在界面线程不能使用Sleep和WaitForSingleObject之类的函数, 使用 MsgWaitForMultipleObjects
http://blog.csdn.net/wishfly/article/details/3726985
你在主线程用了WaitForSingleObject,导致了消息循环的阻塞,界面假死。
然后在线程中调用了SetDlgItemText,而SetDlgItemText实际上调用的是SendMessage,
而SendMessage要等待主线程处理完毕发送的消息才返回继续执行,
而你主线程的消息循环已经阻塞,无法处理消息,导致整个过程“我等你,你等我”,无穷下去
在界面线程不能使用Sleep和WaitForSingleObject之类的函数,
比较好的方法是,将句柄作为线程参数传递进线程,
当线程结束后发一条消息通知主线程处理完毕
在主线程中慎用WaitForSingleObject (WaitForMultipleObjects)
下面的代码我调试了将近一个星期,你能够看出什么地方出了问题吗?
线程函数:
DWORD WINAPI ThreadProc( while(!bTerminate) { // 从一个链表中读取信息并且插入到CListCtrl中 // CListCtrl的句柄是通过线程参数传递进来的 for(;;) { ReadInfoFromList(); InsertToCListCtrl(); } } }
主线程中使用CreateThread启动线程。当想终止子线程时,在主线程中:
bTerminate = TRUE;
WaitForSingleObject(threadHandle, INFINITE);
可是,以运行到WaitForSingleObject,子线程就Crash了。为什么呢?
问题原因:
后来我终于在InsertItem的反汇编中发现了如下的代码
call dword ptr [__imp__SendMessageA@16 (7C141B54h)]
可见,InsertItem是必须借助消息循环来完成任务的。
如果我们在主线程中WaitForSingleObject了,必然导致主线程阻塞,
也就导致了消息循环的阻塞,最终导致工作线程Crash掉了*_*
解决方案:
为了解决在主线程中Wait的问题,微软专门设计了一个函数MsgWaitForMultipleObjects,
这个函数即可以等待信号(thread,event,mutex等等),也可以等待消息(MSG)。
即不论有信号被激发或者有消息到来,此函数都可以返回。呵呵,那么我的解决办法也就出来了。
将上面的WaitForSingleObject用下面的代码替换:
while(TRUE) { DWORD result ; MSG msg ; result = MsgWaitForMultipleObjects(1, &readThreadHandle, FALSE, INFINITE, QS_ALLINPUT); if (result == (WAIT_OBJECT_0)) { break; } else { PeekMessage(&msg, NULL, 0, 0, PM_REMOVE); DispatchMessage(&msg); } }
总结:
如果在工作线程中有可能涉及到了消息驱动的API,
那么不能在主线程中使用WaitForSingleObject一类函数,而必须使用上述的方案。
http://www.midnightbeach.com/jon/pubs/MsgWaits/SynchedThreads_pas.html
unit SynchedThreads; // Copyright © 1998 by Jon Shemitz, all rights reserved. // Permission is hereby granted to freely use, modify, and // distribute this source code PROVIDED that all six lines of // this copyright and contact notice are included without any // changes. Questions? Comments? Offers of work? // mailto:jon@midnightbeach.com - http://www.midnightbeach.com {$T+} {$hints on} {$warnings on} interface uses Windows, Classes, SysUtils, Forms; // Simple threads type TThreadMethod = procedure( Data : pointer ) of object; TSimpleThread = class( TThread ) public constructor CreateSimple( CreateSuspended : boolean; _Action : TThreadMethod; _Data : pointer ); procedure AbortThread; protected ThreadMethod : TThreadMethod; Data : pointer; private procedure Execute; override; end; function RunInThread( Handler : TThreadMethod; Data : pointer ) : TSimpleThread; // Wait threads (basic synchronization) procedure MsgWaitForSingleObject( Handle : THandle ); function SpawnProcess( const Command : string ) : TProcessInformation; type TWaitThread = class( TSimpleThread ) public constructor CreateWait( _Action : TThreadMethod; _Data : pointer ); procedure WaitFor; procedure MsgWaitFor; procedure AbortThread; private AbortFlag : ^boolean; procedure Run( MsgWait : boolean ); end; procedure WaitForThread( Handler : TThreadMethod; Data : pointer ); procedure MsgWaitForThread( var Thread : TWaitThread; Handler : TThreadMethod; Data : pointer ); // Stop/start threads type EAbortedThread = class( Exception ) end; EThreadInUse = class( Exception ) end; TStopStartThread = class( TSimpleThread ) public Waiting : boolean; constructor Create; procedure WaitFor( _Action : TThreadMethod; _Data : pointer ); procedure MsgWaitFor( _Action : TThreadMethod; _Data : pointer ); procedure AbortThread; private Event : THandle; Aborted : boolean; destructor Destroy; override; procedure Execute; override; procedure Run( _Action : TThreadMethod; _Data : pointer; MsgWait : boolean ); end; implementation // TSimpleThread, RunInThread constructor TSimpleThread.CreateSimple( CreateSuspended : boolean; _Action : TThreadMethod; _Data : pointer ); begin ThreadMethod := _Action; // Set these BEFORE calling Data := _Data; // inherited Create()! FreeOnTerminate := True; inherited Create( CreateSuspended ); end; // TSimpleThread.Create procedure TSimpleThread.Execute; begin ThreadMethod( Data ); end; // TSimpleThread.Execute procedure TSimpleThread.AbortThread; begin Suspend; // // Can't kill a running thread by Freeing it Free; // Kills thread end; // TSimpleThread.AbortThread function RunInThread( Handler : TThreadMethod; Data : pointer ) : TSimpleThread; begin Result := TSimpleThread.CreateSimple( False, Handler, Data ); end; // RunInThread // Basic synchronization procedure MsgWaitForSingleObject( Handle : THandle ); begin repeat if MsgWaitForMultipleObjects( 1, Handle, False, INFINITE, QS_ALLINPUT ) = WAIT_OBJECT_0 + 1 then Application.ProcessMessages else BREAK; until True = False; end; // MsgWaitForSingleObject function SpawnProcess( const Command : string ) : TProcessInformation; var StartupInfo : TStartupInfo; begin FillChar( StartupInfo, SizeOf( StartupInfo ), 0 ); // use defaults StartupInfo.cb := SizeOf( StartupInfo ); CreateProcess( nil, PChar( Command ), nil, nil, False, 0, nil, nil, StartupInfo, Result ); end; // SpawnProcess constructor TWaitThread.CreateWait( _Action : TThreadMethod; _Data : pointer ); begin CreateSimple( True, _Action, _Data ); // CreateSuspended AbortFlag := nil; end; // TWaitThread.CreateWait procedure TWaitThread.WaitFor; begin Run( False ); end; // TWaitThread.WaitFor procedure TWaitThread.MsgWaitFor; begin Run( True ); end; // TWaitThread.MsgWaitFor procedure TWaitThread.Run( MsgWait : boolean ); var Aborted : boolean; begin AbortFlag := @Aborted; Aborted := False; Resume; if MsgWait then MsgWaitForSingleObject( Handle ) else inherited WaitFor; if Aborted then Abort; end; // TWaitThread.Run procedure TWaitThread.AbortThread; begin Assert( Assigned( AbortFlag ) ); AbortFlag^ := True; inherited; end; // TWaitThread.CreateWait procedure WaitForThread( Handler : TThreadMethod; Data : pointer ); begin TWaitThread.CreateWait( Handler, Data ).WaitFor; end; // WaitForThread procedure MsgWaitForThread( var Thread : TWaitThread; Handler : TThreadMethod; Data : pointer ); begin Thread := TWaitThread.CreateWait( Handler, Data ); Thread.MsgWaitFor; Thread := nil; end; // MsgWaitForThread // Stop/start threads constructor TStopStartThread.Create; begin Event := CreateEvent( nil, True, False, nil ); // API call is smaller and simpler than Delphi wrapper Assert( Event <> NULL ); Waiting := False; Aborted := False; inherited Create( True ); // Create a suspended thread end; // TStopStartThread.Create destructor TStopStartThread.Destroy; begin CloseHandle( Event ); inherited; end; // TStopStartThread.Destroy procedure TStopStartThread.Execute; begin while not Terminated do begin Assert( Assigned( ThreadMethod ) ); ThreadMethod( Data ); SetEvent( Event ); Suspend; end; end; // TStopStartThread.Execute procedure TStopStartThread.Run( _Action : TThreadMethod; _Data : pointer; MsgWait : boolean ); begin if Waiting then raise EThreadInUse.Create( 'Thread in use' ); if Aborted then raise EAbortedThread.Create( 'Aborted thread' ); ThreadMethod := _Action; Data := _Data; Waiting := True; ResetEvent( Event ); Resume; if MsgWait then MsgWaitForSingleObject( Event ) else WaitForSingleObject( Event, INFINITE ); Waiting := False; if Aborted then Abort; // Raise an EAbort exception end; // TStopStartThread.InternalRun procedure TStopStartThread.MsgWaitFor( _Action : TThreadMethod; _Data : pointer ); begin Run( _Action, _Data, True ); end; // TStopStartThread.Run procedure TStopStartThread.WaitFor( _Action : TThreadMethod; _Data : pointer ); begin Run( _Action, _Data, False ); end; // TStopStartThread.RunBlocking procedure TStopStartThread.AbortThread; begin Suspend; // // Can't kill a running thread by Freeing it Aborted := True; SetEvent( Event ); end; // TStopStartThread.AbortThread end.
// Copyright (C) 2003-2009 Dolphin Project. // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 2.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 2.0 for more details. // A copy of the GPL 2.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official SVN repository and contact information can be found at // http://code.google.com/p/dolphin-emu/ #include "Setup.h" #include "Thread.h" #include "Log.h" #ifdef SETUP_TIMER_WAITING #include <windows.h> #include "ConsoleWindow.h" EventCallBack FunctionPointer[ 10 ]; #endif namespace Common { #ifdef _WIN32 void InitThreading ( ) { // Nothing to do in Win32 build. } CriticalSection::CriticalSection ( int spincount ) { if ( spincount ) { InitializeCriticalSectionAndSpinCount ( §ion, spincount ); } else { InitializeCriticalSection ( §ion ); } } CriticalSection::~CriticalSection ( ) { DeleteCriticalSection ( §ion ); } void CriticalSection::Enter ( ) { EnterCriticalSection ( §ion ); } bool CriticalSection::TryEnter ( ) { return TryEnterCriticalSection ( §ion ) ? true : false; } void CriticalSection::Leave ( ) { LeaveCriticalSection ( §ion ); } Thread::Thread ( ThreadFunc function, void* arg ) : m_hThread ( NULL ), m_threadId( 0 ) { m_hThread = CreateThread ( 0, // Security attributes 0, // Stack size function, arg, 0, &m_threadId ); } Thread::~Thread ( ) { WaitForDeath ( ); } void Thread::WaitForDeath ( const int _Wait ) { if ( m_hThread ) { WaitForSingleObject ( m_hThread, _Wait ); CloseHandle ( m_hThread ); m_hThread = NULL; } } void Thread::SetAffinity ( int mask ) { SetThreadAffinityMask ( m_hThread, mask ); } void Thread::SetCurrentThreadAffinity ( int mask ) { SetThreadAffinityMask ( GetCurrentThread( ), mask ); } // Regular same thread loop based waiting Event::Event ( ) { m_hEvent = 0; #ifdef SETUP_TIMER_WAITING DoneWaiting = false; StartWait = false; hTimer = NULL; hTimerQueue = NULL; #endif } void Event::Init ( ) { m_hEvent = CreateEvent ( NULL, FALSE, FALSE, NULL ); } void Event::Shutdown ( ) { CloseHandle ( m_hEvent ); m_hEvent = 0; } void Event::Set ( ) { SetEvent ( m_hEvent ); } void Event::Wait ( ) { WaitForSingleObject ( m_hEvent, INFINITE ); } inline HRESULT MsgWaitForSingleObject ( HANDLE handle, DWORD timeout ) { return MsgWaitForMultipleObjects ( 1, &handle, FALSE, timeout, 0 ); } void Event::MsgWait ( ) { // Adapted from MSDN example http://msdn.microsoft.com/en-us/library/ms687060.aspx while ( true ) { DWORD result; MSG msg; // Read all of the messages in this next loop, // removing each message as we read it. while ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) { // If it is a quit message, exit. if ( msg.message == WM_QUIT ) return; // Otherwise, dispatch the message. DispatchMessage ( &msg ); } // Wait for any message sent or posted to this queue // or for one of the passed handles be set to signaled. result = MsgWaitForSingleObject ( m_hEvent, THREAD_WAIT_TIMEOUT ); // The result tells us the type of event we have. if ( result == ( WAIT_OBJECT_0 + 1 ) ) { // New messages have arrived. // Continue to the top of the always while loop to // dispatch them and resume waiting. continue; } else { // result == WAIT_OBJECT_0 // Our event got signaled return; } } } /* Separate thread timer based waiting, instead of same thread loop waiting. The downside with this is that it's less convenient to use because we can't stall any threads with a loop. The positive is that we don't cause these incredibly annoying WaitForEternity() hangings. */ #ifdef SETUP_TIMER_WAITING /* I could not figure out how to place this in the class to, CreateTimerQueueTimer() would complain about some kind of type casting, anyone have any ideas about how to do it? */ VOID CALLBACK TimerRoutine ( PVOID lpParam, BOOLEAN TimerOrWaitFired ) { if ( lpParam == NULL ) { DEBUG_LOG ( CONSOLE, "TimerRoutine lpParam is NULL\n" ); } else { // lpParam points to the argument; in this case it is an int // DEBUG_LOG(CONSOLE, "Timer[%i] will call back\n", *(int*)lpParam); } // Call back int Id = *( int* )lpParam; if ( FunctionPointer[ Id ] ) FunctionPointer[ Id ]( ); } // Create a timer that will call back to the calling function bool Event::TimerWait ( EventCallBack WaitCB, int _Id, bool OptCondition ) { Id = _Id; // DEBUG_LOG(CONSOLE, "TimerWait[%i]: %i %i %i\n", Id, StartWait, DoneWaiting, OptCondition); FunctionPointer[ Id ] = WaitCB; // This means we are done waiting, so we wont call back again, and we also reset the variables for this Event if ( DoneWaiting && OptCondition ) { StartWait = false; DoneWaiting = false; FunctionPointer[ Id ] = NULL; // Delete all timers in the timer queue. if ( !DeleteTimerQueue( hTimerQueue ) ) DEBUG_LOG ( CONSOLE, "DeleteTimerQueue failed (%d)\n", GetLastError( ) ); hTimer = NULL; hTimerQueue = NULL; return true; } // Else start a new callback timer StartWait = true; // Create the timer queue if needed if ( !hTimerQueue ) { hTimerQueue = CreateTimerQueue ( ); if ( NULL == hTimerQueue ) { DEBUG_LOG ( CONSOLE, "CreateTimerQueue failed (%d)\n", GetLastError( ) ); return false; } } // Set a timer to call the timer routine in 10 seconds. if ( !CreateTimerQueueTimer( &hTimer, hTimerQueue, ( WAITORTIMERCALLBACK )TimerRoutine, &Id, 10, 0, 0 ) ) { DEBUG_LOG ( CONSOLE, "CreateTimerQueueTimer failed (%d)\n", GetLastError( ) ); return false; } return false; } // Check if we are done or not bool Event::DoneWait ( ) { if ( StartWait && DoneWaiting ) return true; else return false; } // Tells the timer that we are done waiting void Event::SetTimer ( ) { // We can not be done before we have started waiting if ( StartWait ) DoneWaiting = true; } #endif // Supporting functions void SleepCurrentThread ( int ms ) { Sleep ( ms ); } typedef struct tagTHREADNAME_INFO { DWORD dwType; // must be 0x1000 LPCSTR szName; // pointer to name (in user addr space) DWORD dwThreadID; // thread ID (-1=caller thread) DWORD dwFlags; // reserved for future use, must be zero } THREADNAME_INFO; // Usage: SetThreadName (-1, "MainThread"); // // Sets the debugger-visible name of the current thread. // Uses undocumented (actually, it is now documented) trick. // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vsdebug/html/vxtsksettingthreadname.asp void SetCurrentThreadName ( const TCHAR* szThreadName ) { THREADNAME_INFO info; info.dwType = 0x1000; #ifdef UNICODE // TODO: Find the proper way to do this. char tname[ 256 ]; unsigned int i; for ( i = 0; i < _tcslen( szThreadName ); i++ ) { tname[ i ] = ( char )szThreadName[ i ]; // poor man's unicode->ansi, TODO: fix } tname[ i ] = 0; info.szName = tname; #else info.szName = szThreadName; #endif info.dwThreadID = -1; // dwThreadID; info.dwFlags = 0; __try { RaiseException ( 0x406D1388, 0, sizeof( info ) / sizeof( DWORD ), ( ULONG_PTR* )&info ); } __except ( EXCEPTION_CONTINUE_EXECUTION ) { } } // TODO: check if ever inline LONG SyncInterlockedIncrement ( LONG *Dest ) { return InterlockedIncrement ( Dest ); } LONG SyncInterlockedExchangeAdd ( LONG *Dest, LONG Val ) { return InterlockedExchangeAdd ( Dest, Val ); } LONG SyncInterlockedExchange ( LONG *Dest, LONG Val ) { return InterlockedExchange ( Dest, Val ); } #else // !WIN32, so must be POSIX threads pthread_key_t threadname_key; CriticalSection::CriticalSection ( int spincount_unused ) { pthread_mutex_init ( &mutex, NULL ); } CriticalSection::~CriticalSection ( ) { pthread_mutex_destroy ( &mutex ); } void CriticalSection::Enter ( ) { int ret = pthread_mutex_lock ( &mutex ); if ( ret ) ERROR_LOG ( COMMON, "%s: pthread_mutex_lock(%p) failed: %s\n", __FUNCTION__, &mutex, strerror( ret ) ); } bool CriticalSection::TryEnter ( ) { return ( !pthread_mutex_trylock( &mutex ) ); } void CriticalSection::Leave ( ) { int ret = pthread_mutex_unlock ( &mutex ); if ( ret ) ERROR_LOG ( COMMON, "%s: pthread_mutex_unlock(%p) failed: %s\n", __FUNCTION__, &mutex, strerror( ret ) ); } Thread::Thread ( ThreadFunc function, void* arg ) : thread_id ( 0 ) { pthread_attr_t attr; pthread_attr_init ( &attr ); pthread_attr_setstacksize ( &attr, 1024 * 1024 ); int ret = pthread_create ( &thread_id, &attr, function, arg ); if ( ret ) ERROR_LOG ( COMMON, "%s: pthread_create(%p, %p, %p, %p) failed: %s\n", __FUNCTION__, &thread_id, &attr, function, arg, strerror( ret ) ); INFO_LOG ( COMMON, "created new thread %lu (func=%p, arg=%p)\n", thread_id, function, arg ); } Thread::~Thread ( ) { WaitForDeath ( ); } void Thread::WaitForDeath ( ) { if ( thread_id ) { void* exit_status; int ret = pthread_join ( thread_id, &exit_status ); if ( ret ) ERROR_LOG ( COMMON, "error joining thread %lu: %s\n", thread_id, strerror( ret ) ); if ( exit_status ) ERROR_LOG ( COMMON, "thread %lu exited with status %d\n", thread_id, *( int * )exit_status ); thread_id = 0; } } void Thread::SetAffinity ( int mask ) { // This is non-standard #ifdef __linux__ cpu_set_t cpu_set; CPU_ZERO ( &cpu_set ); for ( unsigned int i = 0; i < sizeof( mask ) * 8; i++ ) { if ( ( mask >> i ) & 1 ) { CPU_SET ( i, &cpu_set ); } } pthread_setaffinity_np ( thread_id, sizeof( cpu_set ), &cpu_set ); #endif } void Thread::SetCurrentThreadAffinity ( int mask ) { #ifdef __linux__ cpu_set_t cpu_set; CPU_ZERO ( &cpu_set ); for ( size_t i = 0; i < sizeof( mask ) * 8; i++ ) { if ( ( mask >> i ) & 1 ) { CPU_SET ( i, &cpu_set ); } } pthread_setaffinity_np ( pthread_self( ), sizeof( cpu_set ), &cpu_set ); #endif } void InitThreading ( ) { static int thread_init_done = 0; if ( thread_init_done ) return; if ( pthread_key_create( &threadname_key, NULL /* free */ ) != 0 ) perror ( "Unable to create thread name key: " ); thread_init_done++; } void SleepCurrentThread ( int ms ) { usleep ( 1000 * ms ); } void SetCurrentThreadName ( const TCHAR* szThreadName ) { pthread_setspecific ( threadname_key, strdup( szThreadName ) ); INFO_LOG ( COMMON, "%s(%s)\n", __FUNCTION__, szThreadName ); } Event::Event ( ) { is_set_ = false; } void Event::Init ( ) { pthread_cond_init ( &event_, 0 ); pthread_mutex_init ( &mutex_, 0 ); } void Event::Shutdown ( ) { pthread_mutex_destroy ( &mutex_ ); pthread_cond_destroy ( &event_ ); } void Event::Set ( ) { pthread_mutex_lock ( &mutex_ ); if ( !is_set_ ) { is_set_ = true; pthread_cond_signal ( &event_ ); } pthread_mutex_unlock ( &mutex_ ); } void Event::Wait ( ) { pthread_mutex_lock ( &mutex_ ); while ( !is_set_ ) { pthread_cond_wait ( &event_, &mutex_ ); } is_set_ = false; pthread_mutex_unlock ( &mutex_ ); } LONG SyncInterlockedIncrement ( LONG *Dest ) { #if defined(__GNUC__) && defined (__GNUC_MINOR__) && ((4 < __GNUC__) || (4 == __GNUC__ && 1 <= __GNUC_MINOR__)) return __sync_add_and_fetch ( Dest, 1 ); #else register int result; __asm__ __volatile__ ( "lock; xadd %0,%1" : "=r"( result ), "=m"( *Dest ) : "0"( 1 ), "m"( *Dest ) : "memory" ); return result; #endif } LONG SyncInterlockedExchangeAdd ( LONG *Dest, LONG Val ) { #if defined(__GNUC__) && defined (__GNUC_MINOR__) && ((4 < __GNUC__) || (4 == __GNUC__ && 1 <= __GNUC_MINOR__)) return __sync_add_and_fetch ( Dest, Val ); #else register int result; __asm__ __volatile__ ( "lock; xadd %0,%1" : "=r"( result ), "=m"( *Dest ) : "0"( Val ), "m"( *Dest ) : "memory" ); return result; #endif } LONG SyncInterlockedExchange ( LONG *Dest, LONG Val ) { #if defined(__GNUC__) && defined (__GNUC_MINOR__) && ((4 < __GNUC__) || (4 == __GNUC__ && 1 <= __GNUC_MINOR__)) return __sync_lock_test_and_set ( Dest, Val ); #else register int result; __asm__ __volatile__ ( "lock; xchg %0,%1" : "=r"( result ), "=m"( *Dest ) : "0"( Val ), "m"( *Dest ) : "memory" ); return result; #endif } #endif } // namespace Common