我是一个纯粹的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当时还没有运行......
我希望能够将我的问题转移给专家来理解。
非常感谢, 迈克尔
答案 0 :(得分:1)
关于你问题的第一部分:是的,这将有效。
为了理解它是如何工作的,你需要理解一般的事件处理是如何工作的,而这个特殊的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 :: 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
函数时调用函数。