我最近开始使用QT框架。昨天我开始编写一个简单的多线程应用程序。目前,我有点坚持以下问题。
考虑两个工作者类,它们都使用一个线程来进行一些繁重的计算'。第一个类FooWorker如下所示:
class FooWorker : public QObject
{
Q_OBJECT
public:
FooWorker() : QObject() { }
~FooWorker() { }
signals:
void notify(int);
void aborted();
public slots:
void doWork()
{
int counter = 0;
forever {
// For the sake of this example this reassembles a heavy computational process
if(counter++ < 10) {
emit notify(counter);
QThread::sleep(1);
} else {
counter = 0;
// Wait until we get a signal to restart the process
mutex_.lock();
condition_.wait(&mutex_);
mutex_.unlock();
}
// We should check for a cancellation flag every iteration...
}
emit aborted();
}
private:
QMutex mutex_;
QWaitCondition condition_;
};
插槽&#39; doWork&#39;将被安排在另一个线程中运行。插槽将永远运行,并且每秒发出一个信号,直到发出10个通知。之后我们等到它再次被唤醒。
第二堂课BarWorker看起来像这样:
class BarWorker : public QObject
{
Q_OBJECT
public:
BarWorker() : QObject() { }
~BarWorker() { }
signals:
void aborted();
public slots:
void doWork()
{
forever {
// Another heavy computational process
QThread::sleep(1);
// We should check for a cancellation flag every iteration...
}
emit aborted();
}
void onNotify(int value)
{
qDebug() << "Notification value:" << value;
}
};
再次插槽&#39; doWork&#39;将被安排在另一个线程中运行。该插槽将永远运行以执行繁重的计算过程。一旦完成该过程,我们将一直等到它再次被唤醒(为了这个例子,我把它留在了这个类中)。
最后主要看起来如下:
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QThread* barThread = new QThread();
BarWorker* barWorker = new BarWorker();
barWorker->moveToThread(barThread);
QThread* fooThread = new QThread();
FooWorker* fooWorker = new FooWorker();
fooWorker->moveToThread(fooThread);
// Automatically deletes worker and thread
QObject::connect(fooThread, SIGNAL(started()), fooWorker, SLOT(doWork()));
QObject::connect(fooWorker, SIGNAL(aborted()), fooThread, SLOT(quit()));
QObject::connect(fooWorker, SIGNAL(aborted()), fooWorker, SLOT(deleteLater()));
QObject::connect(fooThread, SIGNAL(finished()), fooThread, SLOT(deleteLater()));
QObject::connect(barThread, SIGNAL(started()), barWorker, SLOT(doWork()));
QObject::connect(barWorker, SIGNAL(aborted()), barThread, SLOT(quit()));
QObject::connect(barWorker, SIGNAL(aborted()), barWorker, SLOT(deleteLater()));
QObject::connect(barThread, SIGNAL(finished()), barThread, SLOT(deleteLater()));
QObject::connect(fooWorker, SIGNAL(notify(int)), barWorker, SLOT(onNotify(int)), Qt::QueuedConnection);
fooThread->start();
barThread->start();
return a.exec();
}
当我运行应用程序时,没有任何内容被打印出来这是预期的,因为BarWorker实例的事件循环被阻止。作为&#39;通知&#39;信号被发出&#39; onNotify&#39; slot排队到事件队列中。因为我们在&#39; doWork&#39;中有一个永无止境的循环(直到我们手动中止它)。插槽,&#39; onNotify&#39;插槽不会被调用。要解决这个问题,我可以做几件事,即:
我希望有人可以为这个问题找到一些替代解决方案,或者甚至提出一个完全不同的方法,因为恕我直言上面的解决方案有些难看并感觉不对。
答案 0 :(得分:5)
我不认为有任何&#34;魔法&#34;解决方案可以在这里找到;如果一个线程正在运行你自己的自定义事件循环,它就无法运行Qt的事件循环。在实践中,有两种常见的解决方案,它们实际上是同一枚硬币的两面:
如您在问题中所建议的,定期从事件循环中调用processEvents(),以便Qt事件处理代码偶尔可以运行并处理传入的异步信号。
doWork()方法中没有长时间运行的循环。相反,做一些简单的工作,将工作的结果/状态存储在成员变量或某个地方,然后调用类似QTimer :: singleShot(0,this,SLOT(doWork())的东西,以便Qt事件第一次调用doWork()后,循环将再次调用doWork()方法。这样,Qt事件循环永远不会被持续超过单个doWork()调用占用的(短暂)时间段。
在这两个选项中,我认为第二个选项更可取,因为它允许Qt事件循环以正常方式运行,并且它还避免了潜在的跳过自己的鞋带问题 - 例如想象一下,如果在使用solution(1)时调用processEvents()会导致调用一个删除BarWorker对象的槽。当processEvents()调用返回时,BarWorker :: doWork()将继续执行,但此时,它可能作为其正常执行的一部分访问的所有本地成员变量和虚拟方法都已被销毁,并且读取或写入它们将导致未定义的行为(如果你很幸运,一个易于调试的崩溃)。使用解决方案(2)时可能会发生这种可能的错误,因为如果在调用doWork()之间删除了BarWorker对象,则可以安全地取消对doWork()的任何排队异步调用。
答案 1 :(得分:1)
与事件循环互操作的forever
循环的习惯用法是零持续时间计时器。我们可以将其分解为WorkerBase
类,其中工作单元将在workUnit
方法中完成:
// https://github.com/KubaO/stackoverflown/tree/master/questions/worker-timer-40369716
#include <QtCore>
// See http://stackoverflow.com/q/40382820/1329652
template <typename Fun> void safe(QObject * obj, Fun && fun) {
Q_ASSERT(obj->thread() || qApp && qApp->thread() == QThread::currentThread());
if (Q_LIKELY(obj->thread() == QThread::currentThread()))
return fun();
struct Event : public QEvent {
using F = typename std::decay<Fun>::type;
F fun;
Event(F && fun) : QEvent(QEvent::None), fun(std::move(fun)) {}
Event(const F & fun) : QEvent(QEvent::None), fun(fun) {}
~Event() { fun(); }
};
QCoreApplication::postEvent(
obj->thread() ? obj : qApp, new Event(std::forward<Fun>(fun)));
}
class WorkerBase : public QObject {
Q_OBJECT
QBasicTimer timer_;
protected:
virtual void workUnit() = 0;
void timerEvent(QTimerEvent *event) override {
if (event->timerId() == timer_.timerId() && timer_.isActive())
workUnit();
}
public:
using QObject::QObject;
Q_SIGNAL void finished();
/// Thread-safe
Q_SLOT void virtual start() {
safe(this, [=]{
timer_.start(0, this);
});
}
/// Thread-safe
Q_SLOT void virtual stop() {
safe(this, [=]{
if (!isActive()) return;
timer_.stop();
emit finished();
});
}
bool isActive() const { return timer_.isActive(); }
~WorkerBase() {
if (isActive()) emit finished();
}
};
工人随后成为:
class FooWorker : public WorkerBase
{
Q_OBJECT
int counter = 0;
bool isDone() const { return counter >= 10; }
void workUnit() override {
if (!isDone()) {
counter ++;
emit notify(counter);
QThread::sleep(1);
} else
stop();
}
public:
void start() override {
counter = 0;
WorkerBase::start();
}
void stop() override {
if (!isDone()) emit aborted();
WorkerBase::stop();
}
Q_SIGNAL void notify(int);
Q_SIGNAL void aborted();
};
class BarWorker : public WorkerBase
{
Q_OBJECT
void workUnit() override {
QThread::sleep(1);
}
public:
void stop() override {
emit aborted();
WorkerBase::stop();
}
Q_SIGNAL void aborted();
Q_SLOT void onNotify(int value)
{
qDebug() << "Notification value:" << value;
}
};
请注意,aborted()
和finished()
信号具有不同的含义。
最后,测试工具:
class Thread : public QThread { public: ~Thread() { quit(); wait(); } };
int main(int argc, char ** argv) {
QCoreApplication app{argc, argv};
BarWorker barWorker;
FooWorker fooWorker;
Thread barThread, fooThread;
barWorker.moveToThread(&barThread);
fooWorker.moveToThread(&fooThread);
barWorker.start();
fooWorker.start();
QObject::connect(&fooWorker, &FooWorker::finished, &app, &QCoreApplication::quit);
QObject::connect(&fooWorker, &FooWorker::notify, &barWorker, &BarWorker::onNotify);
fooThread.start();
barThread.start();
return app.exec();
}
#include "main.moc"
如果您收到QBasicTimer::stop: Failed. Possibly trying to stop from a different thread
警告,则不会产生任何后果,并且是Qt错误的结果。