将 QScopedPointer 移动到线程

Moving a QScopedPointer to Thread

本文关键字:线程 移动 QScopedPointer      更新时间:2023-10-16

上周我读了很多关于RAII的文章,并认为我应该开始在我的应用程序中使用智能指针。例如,我尝试修改我的一个应用程序。它在一个线程中从网络摄像头捕获帧,在另一个线程中执行图像处理,并在 QT 小部件中显示已处理和未处理的图像。一个中心对象是 CCameraHandler,它控制捕获线程和图像处理线程。到目前为止,我在本课程中使用了 4 个普通指针作为成员:

CCameraCapture* m_CameraCapture;
CImageProcessor* m_ImageProcessor;
QThread* m_CameraCaptureThread;
QThread* m_ProcessingThread;

在 CCameraHandler 的构造函数中,我使用 new 创建了实例,并将捕获对象移动到线程中:

m_CameraCaptureThread= new QThread();
m_CameraCapture= new CCameraCapture();
//Move camera capture object to thread
m_CameraCapture->moveToThread(m_CameraCaptureThread);

这种方法效果很好。现在我想用QScopedPointer 进行第一次测试,并m_CameraCapture尝试

使用
QScopedPointer<CCameraCapture> m_CameraCapture;

并在初始化列表中使用 CameraCapture(new CCameraCapture()) 对其进行初始化。它编译得很好,可以正常工作,但是当我关闭应用程序时,调用析构函数时,我从Qt收到错误:"无法将事件发送到其他线程拥有的对象。当前线程 5ff590。接收器"(类型为"CCameraCapture")是在线程 4b7780 中创建的"我想它与 m_CameraCapture->moveToThread(m_CameraCaptureThread);我现在移动作用域指针的位置。QScopedPointer 是否由 CCameraCapture 自动成为父级?到目前为止我使用

//This connections guarantees that the m_CCameraCapture and m_CameraCapture are deleted after calling QThread::exit()
QObject::connect(m_CameraCaptureThread, SIGNAL(finished()), m_CameraCaptureThread, SLOT(deleteLater()));
QObject::connect(m_CameraCaptureThread, SIGNAL(finished()), m_CameraCapture, SLOT(deleteLater()));

在摄像机捕获停止时删除线程。如果m_CameraCapture现在由 CCameraHandler 父级,则可能会导致问题。目前,我不确定在这种情况下使用智能指针是否是一个好主意。任何想法 什么可能导致销毁此错误?

编辑:CCameraHandler dtor 看起来像这样(线程应该在工作线程之前删除):

CCameraHandler::~CCameraHandler(void)
{
//Stop grabbing and processing
emit stopGrabbing();
emit stopProcessing();
/*Wait for the capture thread to terminate. The destructor of CCamera Handler might be called on application close. Therefore it is important to wait for QThreads to terminate. Else the application might close before threads get deleted*/
m_CameraCaptureThread->exit();
m_CameraCaptureThread->wait();
//Wait for the processing thread to terminate
m_ProcessingThread->exit();
m_CameraCaptureThread->wait();
qDebug() << "CCameraHandler deleted";
}

必须销毁已移动到另一个线程的对象:

  1. 从线程本身,或

  2. 从线程本身被破坏的任何线程。

警告:QThread在停止之前被破坏是不安全的。若要对仅运行事件循环的线程安全地执行此操作,应改用以下子类:

class Thread : public QThread {
  using QThread::run; // final
public:
  Thread(QObject * parent = 0) : QThread(parent) {}
  ~Thread() { quit(); wait(); }
};

给定这样一个从 GUI 线程析构的类,您只需要在析构移动到线程的任何对象之前销毁它。当然,根本不需要将此类对象作为指针保存,但是无论您直接持有它们还是将其作为指针持有,下面的代码都将起作用。

class Foo : public Bar {
  CCameraCapture m_CameraCapture;
  CImageProcessor m_ImageProcessor;
  Thread m_CameraCaptureThread;
  Thread m_ProcessingThread;
  ...
}

当类被析构时,将按顺序发生以下情况:

  1. ~Foo()身体运行(可能是空的)。
  2. ...部分中的成员(如果有)将按声明的相反顺序销毁。
  3. m_ProcessingThread.~Thread运行,然后是超类析构函数(~QThread,最后是~QObject)。移动到该线程的任何对象现在都是无线程的。
  4. m_CameraCaptureThread.~Thread运行,然后是超类析构函数。移动到该线程的任何对象现在都是无线程的。
  5. m_ImageProcessor析构函数运行。作为无线程对象,销毁对任何线程都是安全的。
  6. m_CameraCapture析构函数运行。作为无线程对象,销毁对任何线程都是安全的。

如果你用QScopedPointer<...>来保存这些实例,事情会完全一样,只是每个物体的破坏都会被包裹在~QScopedPointer<...>的主体中。

请注意,即使使用原始指针来保存这些实例也是一种过早的悲观:您浪费了一点堆,并且由于额外的间接层,对实例的访问速度有点慢。这些孤立的东西可能不会发挥很大的作用,但如果有成千上万的对象都以这种方式编码,事情就会加起来。

除非绝对必要,否则不要在单个堆块中分配类成员。

问题是您正在从无 UI 线程执行一些 UI 操作。很难确切地说出问题出在哪里,因为您没有提供 CCameraCapture 所做的信息。

我怀疑在捕获帧后,您在标签上设置了一个像素图(以显示帧),而是发出带有新帧的信号并将该信号与UI元素的相应插槽连接。所以我认为示波域指针、信号和插槽与您的问题无关,问题是您没有在需要的地方使用信号插槽机制。