【问题标题】:Type conversion and QThreadStorage warning while using QThread?使用 QThread 时类型转换和 QThreadStorage 警告?
【发布时间】:2016-05-27 09:12:51
【问题描述】:

我对使用QThread 很陌生。我正在使用 QThread 从 Axis Ip Camera 获取图像。在以下代码的 sn-p 中,我将相机类移动到新线程:

QThread camThread;
camera = new AxisNetworkCamera();
camera->moveToThread(&camThread);
camThread.start();
connect(camera, SIGNAL(imageUpdate(QImage)), this, SLOT(upDateImage(QImage)));
connect(camera, SIGNAL(cameraDisconnected()), this, SLOT(cameraDisconnected()));
connect(&camThread, &QThread::finished, &camThread, &QThread::deleteLater);
connect(camera, &AxisNetworkCamera::destroyed, &camThread, &QThread::quit);

我正在调用启动相机的函数:

QMetaObject::invokeMethod(camera, "deviceStartup", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(streamUrl)));

应用程序运行良好,当我关闭它时也可以正常关闭,但我担心的是一些警告消息。

第一个是我启动相机的时候:

Type conversion already registered from type QPair<QByteArray,QByteArray> to type QtMetaTypePrivate::QPairVariantInterfaceImpl

第二个是当我关闭应用程序时:

QThreadStorage: Thread 0x7fffb8004da0 exited after QThreadStorage 7 destroyed

我应该担心这些消息吗?它们,特别是第二个 1,是否意味着任何内存管理不善?

谢谢

【问题讨论】:

    标签: c++ qt qthread


    【解决方案1】:

    您发布的代码毫无意义。 QThread 不是动态分配的,所以你不能 delete 它:deleteLater 调用会崩溃。可能它永远不会被执行,因为你没有显示无论如何都会停止线程的代码。相机被销毁后,再销毁线程也没有意义。

    安全地做事的最简单方法是在你的类中按值持有相机和线程,并以正确的顺序声明它们,以便线程在相机之前被销毁。到那时,相机就变成了无线程的,并且可以安全地在任何线程中销毁。

    还有一种在远程线程中调用方法比使用invokeMethod 更好的方法:

    class Thread : public QThread {
      using QThread::run; // final
    public:
      Thread(QObject*parent = 0): QThread(parent) {}
      ~Thread() { quit(); wait(); }
    };
    
    // See http://stackoverflow.com/a/21653558/1329652 for details about invoke.
    template <typename F> void invoke(QObject * obj, F && fun) {
      if (obj->thread == QThread::currentThread())
        return fun();
      QObject src;
      QObject::connect(&src, &QObject::destroyed, obj, std::forward<F>(fun));
    }
    
    class MyObject : public QObject {
      Q_OBJECT
      AxisNetworkCamera camera;
      Thread camThread { this };
      Q_SLOT void updateImage(const QImage &);
      Q_SLOT void cameraDisconnected();
    public:
      MyObject(QObject * parent = 0) : QObject(parent)
      {
        connect(&camera, &AxisNetworkCamera::imageUpdate, this, &MyObject::updateImage);
        connect(&camera, &AxisNetworkCamera::cameraDisconnected, this, &MyObject::cameraDisconnected);
        camera.moveToThread(&camThread);
        camThread.start();
      }
      void startCamera(const QString & streamUrl) {
        invoke(&camera, [=]{ camera.deviceStartup(streamUrl); });
      }
    };
    

    【讨论】:

    • 如果我正确理解了您的invoke 函数,那么您是在使用src 来确定调用者的线程亲和性,因此deleteLater 信号被正确发送为直接或排队?
    • @JonHarper 关闭但没有骰子 - 是的,它与线程亲和性有关,但与 deleteLater 无关,也与调用者的亲和性无关。在不同的线程中运行一些代码是一种习惯用法。有关详细信息,请参阅this answer。发送一个在析构函数中调用fun 的事件,就完成的动态分配数量而言,开销会略低。
    • 好的。我弄清楚它做了什么,但想确保我理解为什么。这是一段非常聪明和简洁的代码。我喜欢它。
    • @KubaOber 你能否也说明一下我如何在需要时停止线程?谢谢。
    • @the_naive 当然,您可以随时调用thread.quit()。它将退出其事件循环并停止线程。不过,您不必这样做。您可以让相机对象将自身移动到空线程,从而有效地停用它。 [=]{camera.deviceStartup(streamUrl);} 是一个 C++11 lambda。它是大约实例化以下仿函数 class internal { MyObject &amp; obj; QString url; public void operator()() { obj.camera.deviceStartup(url); } internal(MyObject &amp; obj, const QString &amp; url) : obj(obj), url(url) {} }; 的简写