【问题标题】:Using QWidget::update() from non-GUI thread从非 GUI 线程使用 QWidget::update()
【发布时间】:2011-06-21 05:50:53
【问题描述】:

有时我的应用程序在非 GUI 线程中执行的 QWidget::update() 中崩溃。

我正在开发一个应用程序,它从远程主机接收视频帧并将它们显示在 QWidget 上。

为此,我使用了 libVLC 库,它为我提供了一个解码图像。我在 libVLC 回调中收到图像,该回调在单独的 libVLC 线程中执行。在这个回调中,我试图执行 QWidget::update() 方法。有时应用程序崩溃,并且调用堆栈在此方法中的某个位置。这是我的回调代码:

//! Called when a video frame is ready to be displayed, according to the vlc clock. 
//! \c picture is the return value from lockCB().

void VideoWidget::displayCB(void* picture)
{
    QImage* image = reinterpret_cast<QImage*>(picture);

    onScreenPixmapMutex_.lock();
    onScreenPixmap_ = QImage(*image);
    onScreenPixmap_.detach();
    onScreenPixmapMutex_.unlock();

    delete image;

    update();
}

我知道在 Qt 中不允许在主线程之外进行 GUI 操作。但是根据文档 QWidget::update() 只是在 Qt 返回主事件循环时安排一个绘制事件进行处理,并且不会导致立即重绘。

问题是:“不允许在主线程之外进行GUI操作”的规则是否适用于QWidget::update()?该操作是否属于“GUI操作”?

我使用 Qt 4.7.3,崩溃在 Windows 7 和 Linux 上重现。

【问题讨论】:

  • 您确定您的void* picture 确实是QImage*
  • 你用的是什么qt版本?
  • 是的,我在 lockCB() 方法中将此指针传递给 libVLC。
  • 应用程序通常工作正常,但有时会崩溃,并且此类小部件打开的次数越多,崩溃的频率越高。
  • 我认为删除在其他方法中保留的对象不是一个好习惯。是否在代码的其他站点中使用“void* 图片”实例?尽量不要删除收到的“void*图片”,在创建的方法中删除这个对象。问候。

标签: qt thread-safety libvlc


【解决方案1】:

查看Mandelbrot Example。在该示例中,工作线程正在生成图像并将其传递给具有信号/槽机制的渲染小部件。使用相同的方法!

您可以不实现示例中给出的新 updatePixmap() 槽 也可以直接连接小部件的update () 插槽。

从您的代码中,我可以看到您有一个互斥锁来提供并发访问。所以直接使用你的更新槽应该很容易。

这两种方法仍然使用信号/槽机制,因为在 Qt 中不允许在主线程之外进行 GUI 操作。

【讨论】:

  • 谢谢。现在我从非 GUI 线程发出一个信号并将其连接到 QWidget::update() 插槽以获取所需的小部件。崩溃不会重现。
【解决方案2】:

问题是:规则是“GUI 主线程外的操作是 不允许”适用于 QWidget::update()?做这个操作 属于“GUI操作”吗?

是的。更新属于 GUI 操作。根据documentation,所有的QWidget和派生类只能被主线程使用。这是通用的,特定函数可能会声明它们是线程安全的,但在这种情况下 update() 不是,因此从其他线程调用是不安全的。

信号/槽机制起作用是因为 Qt 将(除非另有说明)使用事件来允许一个线程中的槽被另一个线程中的信号触发。如果你使用信号/槽并告诉 Qt 不要做特殊的线程处理,崩溃会再次出现。

【讨论】:

    【解决方案3】:

    any 线程更新像素图所需要做的就是在主线程中执行相关代码。这会处理所有锁定和其他所有问题。现代多线程编程应该很容易:如果不是,你可能会太努力:)

    例如,假设您使用QLabel 来显示图像(为什么要重新发明自己的小部件?!):

    /// This function is thread-safe. It can be invoked from any thread.
    void setImageOn(const QImage & image, QLabel * label) {
      auto set = [image, label]{
        label->setPixmap(QPixmap::fromImage(image));
      };
      if (label->thread() == QThread::currentThread())
        set();
      else {
        QObject sig;
        sig.connect(&sig, &QObject::destroyed, label, set);
      }
    }
    

    现在,您最好做正确的事情并丢弃陈旧的图像 - 如果前面的事件队列中有更新的图像,则设置图像没有意义。这将是使用自定义小部件的唯一原因。详情请见this answer

    旁注(不适用于您的代码):如果您必须从小部件实现的外部 调用QWidget::update,那么您做错了什么。如果您使用的是库存 Qt 小部件,则永远不需要这样做。如果你有自己的小部件并且它需要它的用户调用update,那么你设计错了。仅此而已。

    【讨论】:

      【解决方案4】:

      如果您不想使用信号/槽机制(也可以),请查看我的回答 here

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-09-29
        • 2021-05-16
        • 2011-04-25
        • 1970-01-01
        • 2011-07-28
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多