被阻塞等待异步Qt信号

24

我知道,已经有一些类似下面的问题,但我找不到一个能帮助我的具体答案。所以这是我的问题:

我正在开发一个应用程序,在启动时进行一些GUI初始化操作。其中一件我必须做的事情是调用

NetworkConfigurationManager::updateConfigurations ()

这是一个异步调用,在完成后会触发updateCompleted()信号。问题在于,直到updateConfigurations()完成之前,所有其他GUI初始化都必须等待。

所以我可以做的是:

MyApp::MyApp(QWidget *parent) : ....
{
   doSomeInits();
   //Now connect the signal we have to wait for
   connect(configManager, SIGNAL(updateCompleted()), this, SLOT(networkConfigurationUpdated()));
   configManager->updateConfigurations(); //call the async function
}

void MyApp::networkConfigurationUpdated()
{
   doSomething();
   doRemainingInitsThatHadToWaitForConfigMgr();
}

在我的看法中,分割初始化过程不是一个好的方法。我认为这会使代码变得更加难以阅读 - 初始化应该放在一起。另一件事情是:因为updateConfiguration()是异步的,用户将能够使用GUI,但此时还没有任何信息可供他使用,因为我们正在等待updateCompleted()

那么有没有一种方法可以在应用程序继续之前等待updateCompleted()信号呢?

例如:

MyApp::MyApp(QWidget *parent) : ....
{
   doSomeInits();
   //Now connect the signal we have to wait for
   connect(configManager, SIGNAL(updateCompleted()), this, SLOT(doSomething()));
   ???? //wait until doSomething() is done.
   doRemainingInitsThatHadToWaitForConfigMgr();
}
一些API中有阻塞的替代异步函数,但在这种情况下没有。我感激任何帮助。谢谢!
3个回答

27

做到这一点的方法是使用嵌套事件循环。您只需创建自己的 QEventLoop,将想要等待的任何信号连接到循环的 quit() 槽上,然后执行循环的 exec()。这样,一旦信号被调用,它将触发 QEventLoop 的 quit() 槽,从而退出循环的 exec()

MyApp::MyApp(QWidget *parent) : ....
{
    doSomeInits();
    {
        QEventLoop loop;
        loop.connect(configManager, SIGNAL(updateCompleted()), SLOT(quit()));
        configManager->updateConfigurations(); 
        loop.exec();
    }
    doReaminingInitsThatHadToWaitForConfigMgr();
}

是的,这个方法是可行的,并且是“阻塞等待而不阻塞UI”的标准模式。如果可能的话,我会避免使用它。嵌套事件循环可能会导致严重的问题,例如用户在执行loop.exec()时进行随机未预料到的操作、退出应用程序等,导致exec()返回后应用程序处于未预料到的不一致状态。这就是为什么我建议使用槽并在那里继续,即使这样更冗长。 - Frank Osterfeld
首先,感谢您的帮助。您的答案是正确的,尽管它在我的解决方案中无法工作。我猜我遇到了Frank提到的问题之一,因为我的循环无法退出。尽管如此,我在Nokia Wiki(http://wiki.forum.nokia.com/index.php/TSQ001335_-_Asynchronous_operations_in_S60_and_Qt)中找到了对您答案的证明。所以再次感谢大家! - cyphorious
根据Qt/Nokia的说法,不应使用QEventloop,因为它可能会导致无法控制的递归(请参见http://developer.nokia.com/community/wiki/How_to_wait_synchronously_for_a_Signal_in_Qt)。这仅适用于测试。或者我是否误读了这篇文章? - TSG
这个答案对编写自动化测试用例非常有帮助,谢谢! - vpicaver
Frank是对的 - QEventLoop方法很危险。我通过艰难的方式发现了这一点。我的应用程序需要获取多个网页并按正确顺序处理,逻辑变得非常复杂,从一个函数跳到另一个函数形成状态机,所以我尝试使用QEventLoop。但是,当我这样做时,如果用户正在移动窗口(或进行某些其他操作),事件循环会导致窗口焦点丢失。整个应用程序会冻结。所有应用程序都会冻结!按alt-tab键是唯一恢复控制的方法,然后它就会正常运行。所以...这不是一个好的解决方案。 - Ph0t0n

7

根据chalup回答,如果您要等待一个需要用户注意的时间,您可能希望显示一个进度条(或者也可以是一个启动画面)。

MyApp::MyApp(QWidget *parent) : ....
{
    doSomeInits();
    {
        QSpashScreen splash;
        splash.connect(configManager, SIGNAL(updateCompleted()), SLOT(close()));
        configManager->updateConfigurations(); 
        splash.exec();
    }
    doReaminingInitsThatHadToWaitForConfigMgr();
}

2

另一个解决方案可能是使用QSignalSpy::wait()。这个函数在Qt 5中被引入,正好满足您的需求。

我看到这个类唯一的问题是它提供在QtTest模块中。在我的情况下,当测试代码时,我发现它非常有用,但对于生产代码来说可能不是最好的解决方案。


似乎在内部与chalup的答案相同。 - Maxwell175

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接