我遇到的问题是,当APC排队时,使用SleepEx(INFINITE, true)
发送到睡眠的线程不能可靠地继续。
应用程序场景是,当软件B安装了某个Windows服务时,软件A必须注意。
为此,我创建了一个新线程,通过NotifyServiceStatusChange()
注册了一个回调函数,并通过SleepEx(INFINITE, true)
让线程进入休眠状态。
如果在软件A运行时安装了指定的服务,则调用回调函数,线程继续,并完成其run()
方法。一切都很好。
但是,如果软件A在没有调用回调函数的情况下终止,我仍然希望线程能够正确终止。
Microsoft文档说明了关于SleepEx
函数的如下内容:
当发生下列情况之一时,将继续执行
:
。
因此,我使用QueueUserAPC()
将APC排队到我的线程。这很好,我的函数stopSleeping()
被调用和执行:可以到达一个断点,并且可以在这个函数中进行调试输出。
不幸的是,与我的预期相反,我自己的APC并没有像函数callback()
的调用那样使线程恢复运行。
问题是为什么不呢?
我的线程是从QThread
派生的类,stopThread()
方法由主线程的信号/时隙连接触发。
// **********************************************
void CCheckForService::run()
{
SC_HANDLE SCHandle = ::OpenSCManager( 0
, SERVICES_ACTIVE_DATABASE
, SC_MANAGER_ENUMERATE_SERVICE
);
if ( 0 != SCHandle )
{
meStatus = Status::BEFORE_NOTIFY_SVC_CHANGE;
SERVICE_STATUS_PROCESS ssp;
char text[] = "MyServiceToLookFor";
wchar_t wtext[ 20 ];
mbstowcs( wtext, text, strlen( text ) + 1 );
LPWSTR lpWText = wtext;
SERVICE_NOTIFY serviceNotify = { SERVICE_NOTIFY_STATUS_CHANGE
, &CCheckForIIoT::callback
, nullptr
, 0
, ssp
, 0
, lpWText
};
// Callback function is to be invoked if "MyServiceToLookFor" has been installed
const DWORD result = ::NotifyServiceStatusChange( SCHandle
, SERVICE_NOTIFY_CREATED
, &serviceNotify
);
if ( ERROR_SUCCESS == result )
{
meStatus = Status::WAITING_FOR_CALLBACK;
::SleepEx( INFINITE, true ); // Wait for the callback function
}
LocalFree( lpWText );
}
::CloseServiceHandle( SCHandle );
if ( Status::CANCELLED != meStatus )
{
// inform main thread
emit sendReady( meStatus );
}
}
// **********************************************
// [static]
void CCheckForService::stopSleeping( ULONG_PTR in )
{
Q_UNUSED( in )
meStatus = Status::CANCELLED;
}
// **********************************************
// [static]
void CCheckForService::callback( void* apParam )
{
auto lpServiceNotify = static_cast< SERVICE_NOTIFY* >( apParam );
// the service is now installed; now wait until it runs
{
QtServiceController lBrokerService( "MyServiceToLookFor" );
QTime WaitTime;
WaitTime.start();
while ( !lBrokerService.isRunning() )
{
msleep( 1000 );
// Timeout check
if ( WaitTime.elapsed() > WAIT_FOR_SERVICE_RUN * 1000 )
{
break;
}
}
}
meStatus = Status::OK;
}
// **********************************************
// [SLOT]
void CCheckForService::stopThread( void )
{
HANDLE ThreadHandle( ::OpenThread( THREAD_ALL_ACCESS
, true
, ::GetCurrentThreadId()
)
);
DWORD d = ::QueueUserAPC( &CCheckForIIoT::stopSleeping
, ThreadHandle
, NULL
);
::CloseHandle( ThreadHandle );
}
发布于 2022-01-19 20:38:17
我不能百分之百地肯定这一点,考虑到您没有提供一个可运行的示例,很难验证。
我的猜测是,您将APC调度在主线程上,而不是CCheckForService
线程上。
如果从主线程上的信号/时隙调用CCheckForService::stopThread
,那么它将在主线程上执行。
因此,::GetCurrentThreadId()
将返回主线程的线程id,随后您将使用主线程的线程句柄调用QueueUserAPC()
,因此APC将在主线程上执行。
因此,CCheckForService
将保持睡眠状态,因为它从未收到过APC。
您可以通过将QApplication::instance()->thread()
与CCheckForService::stopSleeping
方法中的QThread::currentThread()
进行比较来验证这一点--如果它们相等,则将APC调度在主线程上而不是工作线程上。
不幸的是,除了调用QThread
之外,QT中没有官方支持的方法来获取QThread::currentThreadId()
的线程id /线程句柄。
因此,您必须将线程id存储在CCheckForService
类中,以便以后可以获得适当的线程句柄,例如:
// TODO: Add to CCheckForService declaration
// private: DWORD runningThreadId;
// TODO: initialize runningThreadId in constructor to 0
// 0 is guaranteed to never be a valid thread id
void CCheckForService::run() {
runningThreadId = ::GetCurrentThreadId();
/** ... Rest of original run() ... **/
}
void CCheckForService::stopThread( void )
{
HANDLE ThreadHandle( ::OpenThread( THREAD_ALL_ACCESS
, true
, runningThreadId /* <------ */
)
);
DWORD d = ::QueueUserAPC( &CCheckForIIoT::stopSleeping
, ThreadHandle
, NULL
);
::CloseHandle( ThreadHandle );
}
不过,本例中仍然存在一个小的争用条件--如果在线程启动之前调用stopThread()
&设置runningThreadId
。在这种情况下,OpenThread()
将失败并返回NULL
,因此排队APC将失败。
https://stackoverflow.com/questions/70776304
复制相似问题