了解 QThread 的事件循环何时从另一个线程开始
在我的程序中,我继承了 QThread,并实现了虚拟方法 run()
,如下所示:
void ManagerThread::run() {
// do a bunch of stuff,
// create some objects that should be handled by this thread
// connect a few signals/slots on the objects using QueuedConnection
this->exec(); // start event loop
}
现在,在另一个线程(我们称之为 MainThread
)中,我启动ManagerThread
并等待其 started()
信号,之后我继续使用应由 ManagerThread 处理的信号和槽
。但是,started() 信号本质上是在调用 run() 之前发出的,因此根据线程调度,我会丢失来自 MainThread 的一些信号,因为事件循环还没有开始!
(编辑:事实证明这不是问题,只是信号没有及时连接,但出于同样的原因)
我可以在调用 exec()
,但这也是自找麻烦。
有没有任何明确/简单的方法可以知道事件循环已经开始?
谢谢!
编辑2:(解决方案)
好吧,事实证明问题并不完全是我所说的。事件循环尚未启动这一事实并不是问题,因为信号应该排队直到事件循环启动为止。问题是,某些信号无法及时连接以被调用 - 因为 started()
信号是在调用 run()
之前发出的。
解决方案是在所有连接之后、执行之前发出另一个自定义信号。这样可以确保所有信号/插槽都已连接。
这是我的问题的解决方案,但并不是线程标题的真正答案。我已经接受了确实回答标题的答案。
我为那些好奇的人留下了下面的所有代码,解决方案是等待 instance()
方法中的另一个信号。
代码:
你们中的许多人都说我不能丢失信号,所以这是我的整个类实现。我将把它简化为最基本的必需品。
以下是 ManagerThread
的接口:
// singleton class
class ManagerThread: public QThread {
Q_OBJECT
// trivial private constructor/destructor
public:
static ManagerThread* instance();
// called from another thread
public:
void doSomething(QString const& text);
// emitted by doSomething,
// connected to JobHandler whose affinity is this thread.
signals:
void requestSomething(QString const& text);
// reimplemented virtual functions of QThread
public:
void run();
private:
static QMutex s_creationMutex;
static ManagerThread* s_instance;
JobHandler* m_handler; // actually handles the requests
};
一些相关的实现。创建线程的单例实例:
ManagerThread* ManagerThread::instance() {
QMutexLocker locker(&s_creationMutex);
if (!s_instance) {
// start socket manager thread, and wait for it to finish starting
s_instance = new ManagerThread();
// SignalWaiter essentially does what is outlined here:
// http://stackoverflow.com/questions/3052192/waiting-for-a-signal
SignalWaiter waiter(s_instance, SIGNAL(started()));
s_instance->start(QThread::LowPriority);
qDebug() << "Waiting for ManagerThread to start";
waiter.wait();
qDebug() << "Finished waiting for ManagerThread thread to start.";
}
return s_instance;
}
重新实现设置信号/槽并启动事件循环的运行:
void ManagerThread::run() {
// we are now in the ManagerThread thread, so create the handler
m_handler = new JobHandler();
// connect signals/slots
QObject::connect(this,
SIGNAL(requestSomething(QString const&)),
m_handler,
SLOT(handleSomething(QString const&)),
Qt::QueuedConnection);
qDebug() << "Starting Event Loop in ManagerThread";
// SOLUTION: Emit signal here and wait for this one instead of started()
this->exec(); // start event loop
}
将处理委托给正确线程的函数。这是哪里 我发出丢失的信号:
void ManagerThread::doSomething(QString const& text) {
qDebug() << "ManagerThread attempting to do something";
// if calling from another thread, have to emit signal
if (QThread::currentThread() != this) {
// I put this sleep here to demonstrate the problem
// If it is removed there is a large chance the event loop
// will not start up in time to handle the subsequent signal
QThread::msleep(2000);
emit(requestSomething(text));
} else {
// just call directly if we are already in the correct thread
m_handler->handleSomething(text);
}
}
最后,这是来自 MainThread
的代码,如果事件循环未及时启动,该代码将会失败:
ManagerThread::instance()->doSomething("BLAM!");
假设处理程序只是打印出其文本,以下是成功运行后打印出来:
等待ManagerThread启动
完成等待 ManagerThread 线程启动。
在 ManagerThread 中启动事件循环
ManagerThread 尝试做某事
哎呀!
以下是运行不成功时会发生的情况:
等待ManagerThread启动
完成等待 ManagerThread 线程启动。
ManagerThread 尝试做某事
在 ManagerThread 中启动事件循环
显然,事件循环是在信号发出后启动的,并且 BLAM 从不打印。 这里有一个竞争条件,需要知道事件循环何时开始, 为了修复它。
也许我错过了一些东西,而问题是不同的......
如果您真的阅读了所有内容,非常感谢!唷!
in my program, I am subclassing QThread, and I implemented the virtual method run()
like so:
void ManagerThread::run() {
// do a bunch of stuff,
// create some objects that should be handled by this thread
// connect a few signals/slots on the objects using QueuedConnection
this->exec(); // start event loop
}
Now, in another thread (let's call it MainThread
), I start the ManagerThread
and wait for its started()
signal, after which I proceed to use the signals and slots that should be handled by ManagerThread
. However, the started()
signal is essentially emmitted right before run()
is called, so depending on thread scheduling I lose some signals from MainThread
, because the event loop hasn't started yet!
(EDIT: turns out that's not the problem, it's just the signals are not connected in time, but for the same reason)
I could emit a signal right before calling exec()
, but that's also asking for trouble.
Is there any definitive/simple way of knowing that the event loop has started?
Thanks!
EDIT2:(SOLUTION)
Alright, so it turns out the problem isn't exactly what I phrased. The fact that the event loop hasn't started isn't the problem, since signals should get queued up until it does start. The problem is, some of the signals would not get connected in time to be called- since the started()
signal is emitted before run()
is called.
The solution is to emit another custom signal after all the connections and right before exec. That way all signals/slots are ensured to be connected.
This is the solution to my problem, but not really an answer to the thread title. I have accepted the answer that does answer the title.
I have left all my code below for those curious, with the solution being, to wait for another signal in the instance()
method.
CODE:
Many of you are saying that I cannot lose signals, so here is my whole class implementation. I will simplify it to just the bare necessities.
Here is the interface to ManagerThread
:
// singleton class
class ManagerThread: public QThread {
Q_OBJECT
// trivial private constructor/destructor
public:
static ManagerThread* instance();
// called from another thread
public:
void doSomething(QString const& text);
// emitted by doSomething,
// connected to JobHandler whose affinity is this thread.
signals:
void requestSomething(QString const& text);
// reimplemented virtual functions of QThread
public:
void run();
private:
static QMutex s_creationMutex;
static ManagerThread* s_instance;
JobHandler* m_handler; // actually handles the requests
};
Some relevant implementations. Creating the singleton instance of the thread:
ManagerThread* ManagerThread::instance() {
QMutexLocker locker(&s_creationMutex);
if (!s_instance) {
// start socket manager thread, and wait for it to finish starting
s_instance = new ManagerThread();
// SignalWaiter essentially does what is outlined here:
// http://stackoverflow.com/questions/3052192/waiting-for-a-signal
SignalWaiter waiter(s_instance, SIGNAL(started()));
s_instance->start(QThread::LowPriority);
qDebug() << "Waiting for ManagerThread to start";
waiter.wait();
qDebug() << "Finished waiting for ManagerThread thread to start.";
}
return s_instance;
}
Reimplementation of run that sets up signals/slots and starts event loop:
void ManagerThread::run() {
// we are now in the ManagerThread thread, so create the handler
m_handler = new JobHandler();
// connect signals/slots
QObject::connect(this,
SIGNAL(requestSomething(QString const&)),
m_handler,
SLOT(handleSomething(QString const&)),
Qt::QueuedConnection);
qDebug() << "Starting Event Loop in ManagerThread";
// SOLUTION: Emit signal here and wait for this one instead of started()
this->exec(); // start event loop
}
Function that delegates the handling to the correct thread. This is where
I emit the signal that is lost:
void ManagerThread::doSomething(QString const& text) {
qDebug() << "ManagerThread attempting to do something";
// if calling from another thread, have to emit signal
if (QThread::currentThread() != this) {
// I put this sleep here to demonstrate the problem
// If it is removed there is a large chance the event loop
// will not start up in time to handle the subsequent signal
QThread::msleep(2000);
emit(requestSomething(text));
} else {
// just call directly if we are already in the correct thread
m_handler->handleSomething(text);
}
}
Finally, here is the code from MainThread
that will fail if the event loop doesn't start in time:
ManagerThread::instance()->doSomething("BLAM!");
Assuming that the handler just prints out its text, here is what gets printed out on a successful run:
Waiting for ManagerThread to start
Finished waiting for ManagerThread thread to start.
Starting Event Loop in ManagerThread
ManagerThread attempting to do something
BLAM!
And here is what happens on an unsuccessful run:
Waiting for ManagerThread to start
Finished waiting for ManagerThread thread to start.
ManagerThread attempting to do something
Starting Event Loop in ManagerThread
Clearly the event loop started after the signal was emitted, and BLAM never prints.
There is a race condition here, that requires the knowledge of when the event loop starts,
in order to fix it.
Maybe I'm missing something, and the problem is something different...
Thanks so much if you actually read all that! Phew!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
如果连接设置正确,就不应该丢失信号。但是如果您确实想获得有关线程事件循环开始的通知,您可以尝试
QTimer::singleShot()
在调用exec()
之前的run()
中。它将在事件循环启动时传递,并且仅传递一次。If you setup the connections right, you shouldn't be losing the signals. But if you really want to get a notice on the start of the thread's event loop, you can try
QTimer::singleShot()
in yourrun()
right before callingexec()
. It will be delivered when the event loop starts and only delivered once.您可以查看
QSemaphore
在线程之间发出信号。插槽和信号更适合同一线程上的 ui 事件和回调。编辑:或者,如果信号量不适用,您可以将 QMutex 与 QWaitCondition 结合起来。更多示例代码将有助于了解如何将 ManagerThread 与 MainThread 结合使用。
You could look at
QSemaphore
to signal between threads. Slots and signals are better for ui events and callbacks on the same thread.Edit: Alternately you could combine
QMutex
withQWaitCondition
if a semaphore is not applicable. More example code to see how you are using the ManagerThread in conjunction with the MainThread would be helpful.这不是问题。线程之间的信号排队(更具体地说,您需要将它们设置为在
connect()
调用中排队,因为线程之间的直接连接并不安全)。http://doc.qt.io /qt-5/threads-qobject.html#signals-and-slots-across-threads
This is a non-issue. Signals between threads are queued (more specifically, you need to set them up to be queued in the
connect()
call because direct connections between threads aren't safe).http://doc.qt.io/qt-5/threads-qobject.html#signals-and-slots-across-threads
您可以在 ManagerThread 的构造函数中创建信号/槽连接。这样,即使在调用 run() 之前,它们也肯定已连接。
You could create the signal/slots connections in the constructor of the ManagerThread. In that way, they are certainly connected even before run() is called.