首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Windows SleepEx()在排队时不会恢复

Windows SleepEx()在排队时不会恢复
EN

Stack Overflow用户
提问于 2022-01-19 18:58:02
回答 1查看 184关注 0票数 1

我遇到的问题是,当APC排队时,使用SleepEx(INFINITE, true)发送到睡眠的线程不能可靠地继续。

应用程序场景是,当软件B安装了某个Windows服务时,软件A必须注意。

为此,我创建了一个新线程,通过NotifyServiceStatusChange()注册了一个回调函数,并通过SleepEx(INFINITE, true)让线程进入休眠状态。

如果在软件A运行时安装了指定的服务,则调用回调函数,线程继续,并完成其run()方法。一切都很好。

但是,如果软件A在没有调用回调函数的情况下终止,我仍然希望线程能够正确终止。

Microsoft文档说明了关于SleepEx函数的如下内容:

当发生下列情况之一时,将继续执行

  • 调用I/O完成回调函数。
  • 是异步过程调用(APC),排队到线程。
  • 超时间隔。

因此,我使用QueueUserAPC()将APC排队到我的线程。这很好,我的函数stopSleeping()被调用和执行:可以到达一个断点,并且可以在这个函数中进行调试输出。

不幸的是,与我的预期相反,我自己的APC并没有像函数callback()的调用那样使线程恢复运行。

问题是为什么不呢?

我的线程是从QThread派生的类,stopThread()方法由主线程的信号/时隙连接触发。

代码语言:javascript
运行
复制
// **********************************************
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 );
}
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 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类中,以便以后可以获得适当的线程句柄,例如:

代码语言:javascript
运行
复制
// 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将失败。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70776304

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档