我遇到的問題是,SleepEx(INFINITE, true)當 APC 排隊時,使用發送到睡眠狀態的執行緒不能可靠地繼續。
應用場景是當軟體B安裝了某個Windows服務時,軟體A必須注意。
為此,我創建了一個新執行緒,通過 注冊一個回呼函式NotifyServiceStatusChange(),并通過讓執行緒休眠SleepEx(INFINITE, true)。
如果在軟體A運行期間安裝了指定的服務,則呼叫回呼函式,繼續執行緒,并完成其run()方法。一切正常。
但是,如果軟體 A 在沒有呼叫回呼函式的情況下終止,我仍然希望執行緒正確終止。
Microsoft 檔案對此SleepEx功能進行了說明:
當發生以下情況之一時,執行恢復:
- 呼叫 I/O 完成回呼函式。
- 異步程序呼叫 (APC) 排隊到執行緒。
- 超時間隔已過。
因此,我使用QueueUserAPC(). 這作業正常,我的函式stopSleeping()被呼叫并執行:可以到達斷點并且可以在此函式內進行除錯輸出。
不幸的是,出乎我的意料,我自己的 APC 并沒有像函式呼叫那樣使執行緒恢復運行callback()。
問題是,為什么不呢?
我的執行緒是一個派生自的類QThread,該stopThread()方法由來自主執行緒的 SIGNAL/SLOT 連接觸發。
// **********************************************
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 );
}
uj5u.com熱心網友回復:
鑒于您沒有提供難以驗證的可運行示例,我對此不是 100% 確定。
我的猜測是你在主執行緒而不是執行緒上調度 APC CCheckForService。
如果CCheckForService::stopThread從主執行緒上的信號/插槽呼叫,那么它將在主執行緒上執行。
So::GetCurrentThreadId()將回傳主執行緒的執行緒 ID,然后您最終會呼叫QueueUserAPC()主執行緒的執行緒句柄,因此 APC 將在主執行緒上執行。
所以CCheckForService將保持休眠狀態,因為它從未收到過 APC。
QApplication::instance()->thread()您可以通過與QThread::currentThread()您的方法內部進行比較來驗證這一點CCheckForService::stopSleeping- 如果它們相等,您將 APC 安排在主執行緒而不是作業執行緒上。
不幸的是QThread,除了呼叫QThread::currentThreadId().
因此,您必須將執行緒 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://www.uj5u.com/qukuanlian/417313.html
標籤:
