Qt同步方法

时间:2015-03-27 09:57:15

标签: qt

我是一个纯粹的Qt初学者,目前想了解它的基本概念以及如何以正确的方式使用它们。我的问题可能看起来有些“褴褛”,我想提前为此道歉。话虽如此,问题是:

我想实现一个方法,它“阻塞”直到处理完成(出于什么原因......)。为了做内部,我必须使用一个异步类,当它在后台完成它的工作时发出一个信号。我想以阻止的方式使用它:

QEventLoop myLoop;

workerClass x;

QObject::connect(x, SIGNAL(finished()), &myLoop, SLOT(quit()));

/* x is asnchrounous and will emit "finished" when ist job is done */
x.doTheJob();

myLoop.exec();

那会有用吗?根据其他一些帖子 - 它应该。但到底发生了什么?

当刚刚创建了一个新的QEventLoop实例时,是否自动意味着myLoop发出的信号会在新的处理程序中自动处理?此外,循环在开始工作后开始。在myLopp启动之前,当它已经“完成”时会发生什么?

一位同事告诉我,我可以使用

QEventLoop myLoop;

workerClass x;

QObject::connect(x, SIGNAL(finished()), &myLoop, SLOT(quit()));

/* doTheJob is a slot now */
QMetaObject::invokeMethod(x, "doTheJob", Qt::QueuedConnection);

myLoop.exec();

我理解,在这种情况下,doTheJob是从事件循环中调用的。但是从哪个事件循环? invokeMethod()不会被告知在特定事件循环(myLoop)中调用该方法,那么为什么要将事件发布到myLoop而不是“外部”循环呢?事实上,myLoop当时还没有运行......

我希望能够将我的问题转移给专家来理解。

非常感谢, 迈克尔

2 个答案:

答案 0 :(得分:1)

关于你问题的第一部分:是的,这将有效。

实际上a class for it in Qxt

为了理解它是如何工作的,你需要理解一般的事件处理是如何工作的,而这个特殊的hack虽然是一般的坏主意,但却是学习它的好方法。

让我们用一个主循环对一个简化的调用堆栈进行映像:

6 myslot()
5 magic
4 someEvent.emit
3 eventloop.exec
2 qapplication.exec
1 main

只要你不从myslot()返回,“魔法”就不能调用连接到同一信号的任何其他插槽,也不能处理其他信号。

现在,使用嵌套的eventloop“signalwaiter”hack:

10 myslot2()
 9 magic
 8 someEvent2.emit
 7 eventloop.exec
 6 myslot()
 5 magic
 4 someEvent.emit
 3 eventloop.exec
 2 qapplication.exec
 1 main

我们仍然无法继续处理我们阻止的事件,但我们可以处理新事件,例如您要连接到退出的信号。

这不是可重入的,通常是一个非常糟糕的主意。您最终可能会很难调试代码,因为您可能正在从堆栈中调用您不期望的插槽。

避免fire-before-exec问题的方法是将信号连接到设置标志的插槽。如果在exec之前设置了标志just don't exec

但是,为了便于解释,让我们看看你的方法是如何工作的(它确实有效):

当您使用Qt :: QueuedConnection时,信号发射不再是堆叠调用。看起来更像是这样:

5 eventloop.putQueue
4 someSignal.emit
3 eventloop.exec
2 qapplication.exec
1 main

然后发出回报

3 eventloop.exec
2 qapplication.exec
1 main

和eventloop将你的插槽放在堆栈上

5 myslot()
4 eventloop.pop
3 eventloop.exec
2 qapplication.exec
1 main

因此,在调用插槽时,您的信号已从堆栈中删除,所有局部变量都消失了。 这与DirectConnection有很大不同,对于理解QObject::deleteLater()

等其他内容非常重要

回答哪个 eventloop执行你的队列的问题:堆栈顶部的那个。

答案 1 :(得分:1)

关键是QObject::connect

QObject::connect(x, SIGNAL(finished()), &myLoop, SLOT(quit()));

此函数表示发出finished()时,应在事件发生后立即调用myLoop.quit()。所以在你的例子中,

<--------thread A-------->  |  <--------thread B-------->
   QEventLoop myLoop;
   workerClass x;
   x.doTheJob();                 starts thread B
   sleep in exec()               workerClass::JobA();
   sleeping.....                 workerClass::JobB();
   sleeping.....                 workerClass::JobC();
   sleeping.....                 workerClass::JobD();
   sleeping.....                 workerClass::finished();
   sleeping.....       
   wake up by quit();
在线程A休眠之后必须调用

myLoop.quit,否则线程A可能会永远睡眠,因为在睡眠之前可能会调用quit。你必须找到一种方法来指定它。但是怎么样?看看QObject::connect,最后一个参数是

Qt::ConnectionType type = Qt::AutoConnection

关于Qt::AutoConnection

  

(默认)如果接收器位于发出信号的线程中,则使用Qt :: DirectConnection。否则,使用Qt :: QueuedConnection。

信号在线程B中发出,接收器myLoop在线程A中,这意味着您正在使用Qt :: QueuedConnection:

  

当控制返回到接收者线程的事件循环时,将调用该槽。插槽在接收器的线程中执行。

当控件返回到事件循环myLoop.quit时,将调用插槽myLoop.exec。换句话说,仅在quit运行时调用exec。这正是我们想要的,一切正常。因此,您的第一个示例始终正确运行,因为信号和插槽使用Qt::QueuedConnection(默认值Qt::AutoConnection)连接在一起。 你的第二个例子工作得很好,因为QObject::connect也是一样的。

如果将其更改为Qt::DirectConnection,则故事就不同了。在线程B中调用quit,有可能在quit之前调用exec并且线程A将永远休眠。因此,在这种情况下,您永远不应该使用Qt::DirectConnection

故事讲述的是QObject::connect和线程。 QMetaObject::invokeMethod与此无关,它只是在线程执行exec函数时调用函数。