【问题标题】:Knowing when a QThread's event loop has started from another thread知道 QThread 的事件循环何时从另一个线程开始
【发布时间】:2011-07-12 19:57:49
【问题描述】:

在我的程序中,我继承了 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() 之前发出信号,但这也是自找麻烦。

是否有任何确定/简单的方法可以知道事件循环已经开始?

谢谢!

EDIT2:(解决方案)

好吧,事实证明问题并不完全是我所说的。事件循环尚未开始的事实不是问题,因为信号应该在它开始之前排队。问题是,一些信号无法及时连接到被调用——因为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 试图做某事
BLAM!

以下是不成功运行时会发生的情况:

等待 ManagerThread 启动
已完成等待 ManagerThread 线程启动。
ManagerThread 试图做某事
在 ManagerThread 中启动事件循环

显然,事件循环在信号发出后开始,并且 BLAM 永远不会打印。 这里有一个竞争条件,需要知道事件循环何时开始, 为了修复它。

也许我遗漏了一些东西,而问题是不同的......

如果您真的阅读了所有内容,非常感谢!呸!

【问题讨论】:

  • 您确定正在丢失信号吗?如果信号是从线程内连接的,则它们被设置为排队信号。如果它们是从没有线程的情况下连接的,它们是同步信号。查看场景的其余部分以了解信号的连接方式/时间会很有帮助。
  • @lefticus 我已经用查看问题所需的代码更新了我的帖子。最后,我包含了两次程序运行的输出。一个成功(及时创建事件循环),另一个失败(在创建事件循环之前信号丢失)。

标签: c++ multithreading qt qthread


【解决方案1】:

您可以查看QSemaphore 以在线程之间发出信号。插槽和信号更适合同一线程上的 ui 事件和回调。

编辑:或者,如果信号量不适用,您可以将 QMutexQWaitCondition 组合使用。更多示例代码可以帮助您了解如何将 ManagerThread 与 MainThread 结合使用。

【讨论】:

  • 我可以在MainThread 的信号量上尝试acquire()(因此等待),但关键是,信号量上的release() 会告诉我ManagerThread' s 事件循环已经开始了吗?
  • connect() 方法是线程安全的,但是从QObject 派生的对象不是你可能需要某种锁定机制,即使在connect() 中使用Qt::QueuedConnection 时也是如此……为什么否决票?
  • 任何并行编程都需要了解数据同步。但是,在许多情况下,您不需要对数据对象本身进行锁定(管道模型)。 QT 中的信号/槽机制正是跨线程提供的,因为它是一个线程安全队列。跨线程通信也很容易使用它们,因为它是一个线程安全队列。坦率地说,说不使用它是错误的,尤其是考虑到整个框架都是基于它们的。
  • 好吧,我有点含糊其辞。我并不是要暗示根本不使用它们。
【解决方案2】:

这不是问题。线程之间的信号是排队的(更具体地说,您需要将它们设置为在 connect() 调用中排队,因为线程之间的直接连接不安全)。

http://doc.qt.io/qt-5/threads-qobject.html#signals-and-slots-across-threads

【讨论】:

  • 自动(默认)如果连接发生在线程之间,则选择排队连接,如您指向的文档所示。
  • 谢谢 - 我在 QT 工作已经有一段时间了。虽然我认为我总是明确设置它,原因很简单,当我查看代码时,我确切地知道它在做什么。
  • @Brian Roach 我已经发布了有问题的代码,所有证据都表明在事件循环开始之前信号丢失了。感谢您的关注。
  • 我不认为这段代码做你认为的那样。您的所有对象都归主 QT 线程所有(您在 run() 中创建的任何内容等)。查看文档中的线程亲和性(在我看来这很奇怪,但它就是这样)。我必须查看一个旧项目以了解细节,但我最终创建了一个 QThread 的子类,该子类将“worker”对象作为构造函数的参数并在那里更改了亲和力。
  • @Brian Roach 没关系,你是对的,这不是问题。问题是信号/插槽在某些情况下被调用之前没有连接。我需要在它们连接后发出一个信号,然后等待。无需知道事件循环何时开始。谢谢!
【解决方案3】:

如果您正确设置连接,则不应丢失信号。但是如果你真的想在线程事件循环开始时得到通知,你可以在调用exec()之前在run()中尝试QTimer::singleShot()。它会在事件循环开始时交付,并且只交付一次。

【讨论】:

  • 事实证明我的问题并不是我问的,所以我不需要知道事件循环何时开始。但是,由于这是问题的标题,因此这将是正确的答案。特别是,QTimer::singleShot() 可以发出一个信号,应该等待。当线程进入事件循环时,QTimer被处理,信号被触发,我们就知道事件循环已经开始了!谢谢!
【解决方案4】:

您可以在 ManagerThread 的构造函数中创建信号/插槽连接。这样,即使在调用 run() 之前,它们也肯定是连接的。

【讨论】:

  • 问题是我需要在ManagerThread 线程中创建JobHandler(注意在类的方法中创建与在特定线程中实际创建之间的区别)。因此,我需要在run() 方法中创建JobHandler 实例,它将在正确的线程中运行。我无法在构造函数中建立连接,因为 JobHandler 实例还不存在。
  • @Alexander 启动时不能用MoveToThread把JobHandler移动到线程吗?
  • 我想这是可能的。问题已经解决了,谢谢建议。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-17
  • 2012-02-11
  • 1970-01-01
  • 1970-01-01
  • 2021-06-29
相关资源
最近更新 更多