Использование QWidget::update() из потока без графического интерфейса

Иногда мое приложение вылетает в QWidget::update(), который работает в потоке без GUI.

Я разрабатываю приложение, в котором получает видеокадры с удаленного хоста и отображает их на QWidget.

Для этого я использую библиотеку libVLC, которая дает мне декодированное изображение. Я получаю изображение в обратном вызове libVLC, которое выполняется в отдельном потоке libVLC. В этом обратном вызове я пытаюсь выполнить метод QWidget::update(). Иногда происходит сбой приложения, и callstack находится где-то в этом методе. Вот мой код обратного вызова:

//! 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. Но согласно документации, QWidget::update() просто планирует событие рисования для обработки, когда Qt возвращается в основной цикл обработки событий и не вызывает немедленного перерисовывания.

Вопрос: применимо ли правило "Операции с графическим интерфейсом вне основного потока" для QWidget::update()? Эта операция относится к "операциям с графическим интерфейсом"?

Я использую Qt 4.7.3, сбой воспроизводится в Windows 7 и Linux.

3 ответа

Решение

Вопрос в следующем: применимо ли правило "Операции с графическим интерфейсом вне основного потока" для QWidget::update()? Эта операция относится к "операциям с графическим интерфейсом"?

Да. Обновление относится к операциям GUI. Согласно документации, все QWidget и производные классы могут использоваться только основным потоком. Это является общим, и определенные функции могут утверждать, что они потокобезопасны, но в этом случае update () этого не делает, поэтому небезопасно вызывать из других потоков.

Механизм сигнал / слот работает, потому что Qt будет (если не указано иное) использовать события, чтобы позволить слотам в одном потоке запускаться сигналами в другом. Если бы вы использовали сигналы / слоты и сказали Qt не выполнять специальную обработку потоков, аварийные ситуации появятся снова.

Посмотрите на пример Мандельброта. В этом примере рабочий поток генерирует изображение и передает его в виджет рендеринга с механизмом сигнал / слот. Используйте тот же метод!

Вместо реализации нового слота updatePixmap(), как показано в примере, вы также можете напрямую подключить слот update () вашего виджета.

Из вашего кода я вижу, что у вас есть мьютекс для одновременного доступа. Так что должно быть легко напрямую использовать ваш слот обновления.

Оба метода все еще используют механизм сигнал / слот, потому что операции с графическим интерфейсом вне основного потока не разрешены в Qt.

Все, что вам нужно сделать, чтобы обновить растровое изображение из любого потока, это выполнить соответствующий код в основном потоке. Это берет на себя все блокировки и все остальное. Предполагается, что современное многопоточное программирование должно быть простым: если это не так, возможно, вы слишком стараетесь:)

Например, предположим, что вы используете 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);
  }
}

Теперь вы могли бы также сделать все правильно и удалить устаревшие изображения - нет смысла устанавливать изображение, если в очереди событий есть более новые изображения. Это было бы единственной причиной для использования собственного виджета. Смотрите этот ответ для деталей.

Примечание (не относится к вашему коду): если вам нужно позвонить QWidget::update извне реализации виджета вы делаете что-то очень неправильное. Если вы используете стандартный виджет Qt, вам никогда не нужно это делать. Если у вас есть собственный виджет, и ему нужен пользователь для вызова update на нем вы ошиблись. Это все, что нужно.

Проверьте мой ответ здесь, если вы не хотите использовать механизм сигнал / слот (который также работает).

Другие вопросы по тегам