【问题标题】:Using QThread and moveToThread properly with QTimer and QTcpSocket正确使用 QThread 和 moveToThread 与 QTimer 和 QTcpSocket
【发布时间】:2013-10-15 15:58:29
【问题描述】:

从阅读blogblog 和其他一些内容来看,子类化 QThread 是不好的做法。所以我尝试应用这个方法。

但我的问题是我想移动到另一个线程的类中有一个 QTimer 和一个 QTcpSocket。突然之间,它并不像使用的示例那么容易。 :(

QThread m_commsThread;
m_pICommsHandler = new CommsHandlerIP();
m_pICommsHandler->moveToThread(&m_commsThread);
m_commsThread.start();

这里是 CommsHandlerIP 类,方法不包括在内。

class CommsHandlerIP : public QObject
{
    Q_OBJECT
public:
    CommsHandlerIP();
    ~CommsHandlerIP(void);
protected:
    QTcpSocket m_TCPSocket;
    QTimer m_timer;
}

问题是 QTimer 和 QTcpSocket(在 CommsHandlerIP 类中)在主线程中,即使你移动了 CommsHandlerIP。所以我无法启动计时器或连接套接字。

如果我尝试将 QTimer 和 QTcpSocket 移动到线程(例如在构造函数中通过传递线程指针),当我离开应用程序时,这会变得非常混乱。

我该怎么办?

【问题讨论】:

    标签: c++ multithreading qt qthread


    【解决方案1】:

    我在搜索 Timer 行为和 movetoThread 时偶然发现了这一点。 接受的答案是一个很好的解决方法,但不是问题的根本原因。一般规则是,当您移动一个对象时,所有子对象也会随之移动。所以你只需要确保 QTimer 成为一个孩子,然后在它的构造函数中传递 this 指针。

    CommsHandlerIPL::CommsHandlerIP()
    : QObject(), m_pTimer(new QTimer(this))        // <=== crucial to make it a "child" object 
    {
    }
    

    【讨论】:

      【解决方案2】:

      类实例在调用线程上创建。 QTimer 继承 QObjectQt 上的每个线程如果调用exec(),则可以有一个事件循环。 所以你想将QTimer 移动到另一个线程上的事件循环中。 所以你应该手动移动它。

      因此,将它们的创建延迟到移动对象之后:-

      class CommsHandlerIP : public QObject
      {
          Q_OBJECT
      
          public slots:
             void Initialise();
      
          private: 
             void Run();
      
             // c++ 11, initialising in headers...
             QTimer* m_pTimer = NULL;
             QTcpSocket* m_pSocket = NULL;   
      };
      
      void CommsHandlerIP::Initialise()
      {
           m_pTimer = new QTimer(this);
           m_pSocket = new QTcpSocket(this);
      
           Run();
      }
      
      QThread m_commsThread;
      m_pICommsHandler = new CommsHandlerIP();
      
      // Note Qt 5 connect style
      connect(&m_commsThread, &QThread::started, m_pICommsHandler, &CommsHandlerIP::Initialise);
      m_pICommsHandler->moveToThread(&m_commsThread);
      m_commsThread.start();
      

      线程启动时,调用CommsHanderIPInitialise函数;这是您应该在调用Run() 之前创建和设置QTcpSocketQTimer 对象的地方。由于CommsHandlerIP 在创建这些对象之前在新线程中运行,它们也将共享相同的线程亲缘关系。

      【讨论】:

      • 我觉得很愚蠢,我知道问题出在哪里,但我试图以完全错误的方式解决它
      • 当你知道怎么做时很容易;O)
      • 在构造函数中创建 this 作为父级传递给 m_TCPSocketm_timer 就足够了。这是因为QObject::moveToThread()changes the thread affinity for this object and its children。所以你不必延迟他们的创作。
      • 是的,但是如果在移动信号/插槽之前已经对它们进行了连接,则问题可能会在以后出现。创建的连接类型取决于调用者和被调用者的线程亲和性。
      • @Merlin069 AFAIK,如果类型是Qt::AutoConnection,则在调用moveToThread() 之前是否建立连接无关紧要。这是因为If the signal is emitted from a different thread than the receiving object, the signal is queued, behaving as Qt::QueuedConnection. Otherwise, the slot is invoked directly, behaving as Qt::DirectConnection. The type of connection is determined when the signal is emitted.
      【解决方案3】:

      使用QRunnableQThreadPool,有一种更简单的方法可以实现所有这些,它遵循相同的算法,但不涉及创建线程和更改线程亲缘关系所需的所有样板化

      如果我转换 Merlin069 的示例,您会看到它是如何简化代码的:

      class CommsHandlerIP : public QObject, public QRunnable
      {
          Q_OBJECT
          public:
             void run();
      
          public slots:
              //... any slots
      
          signals:
              //... any signals
      
          private:
             // c++ 11, initialising in headers...
             QTimer* m_pTimer = NULL;
             QTcpSocket* m_pSocket = NULL;   
      };
      
      void CommsHandlerIP::run()
      {
           m_pTimer = new QTimer();
           m_pSocket = new QTcpSocket();
      
           //...
      
           delete m_pTimer;
           delete m_pSocket;
      }
      
      QThreadPool::globalInstance()->start(new CommsHandlerIP);
      

      【讨论】:

        猜你喜欢
        • 2017-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-12-17
        • 2018-09-01
        • 2017-01-28
        • 2012-06-03
        • 1970-01-01
        相关资源
        最近更新 更多