Qt: Recursive repaint detected
-
Hallo,
ich schreibe einen Datei-Manager, der auch rumkopieren und verschieben kann. Wenn eine Kopieraktion startet, wird ein Thread und einen Fortschrittsbalken angezeigt. Wenn ich nach dem Fehler google, sehe ich bloß Threads in denen steht, man solle den Signals/Slot-Mechanismus innerhalb eines Threads benutzen, wenn man von da aus an einem Widget werkeln will. Jedoch benutze ich überwiegend den Signals/Slots-Mechanismus und werkle kein einziges Mal innerhalb des Threads an einem Widget. Hier ein paar Zeilen Code...
#include "file_operation.hpp" #include <boost/system/error_code.hpp> /*** COPY OPERATION ***/ void copy_operation::copy_file(const fs::path& source, const fs::path& destination){ ... emit message("Copying " + QString{source.c_str()}); ... emit processed(); } void copy_operation::copy(const QStringList& files, const QDir& output) try{ for(const auto& file : files) copy_file(file.toStdString(), output.path().toStdString()); } catch(const std::exception& err){ emit error(err.what()); } /*** RENAME OPERATION ***/ void rename_operation::check_error(const fs::path& file_path, const QDir& output){ ... } void rename_operation::rename(const QStringList& files, const QDir& output) try{ ... for(const auto& file : files){ ... emit message("Moving " + file); ... emit processed(); } } catch(const std::exception& err){ emit error(err.what()); } /*** REMOVE OPREATION ***/ void remove_operation::remove(const QStringList& files) try{ for(const auto& file : files){ ... emit processed(); } } catch(const std::exception& err){ emit error(err.what()); }
Wie man sieht benutze ich ausschließlich Signals um den Fortschrittsbalken zu verändern. Der ausgepunktete Rest sind die Operationen die mittels Boost.Filesystem durchgeführt werden.
Hier noch der Code, an dem Fortschittsbalken und Operation erstellt werden:auto* operation = new copy_operation{files, dir_info.directory}; auto* widget = new file_operation_widget{operation, count}; connect(widget, &file_operation_widget::on_close, widget, &QWidget::deleteLater); connect(operation, &QThread::finished, operation, &QObject::deleteLater); widget->show(); operation->start();
Liegt der Fehler hier vielleicht? Ich erstelle hier die Kopieroperation und den Fortschrittsbalken. In der Klasse des Fortschrittbalkens habe ich closeEvent() überschrieben, der zusätzlich einfach nur ein Signal namens on_close() auslöst. Dieses Signal verbinde ich mit deleteLater() um mir das Memory Leak zu ersparen. Beim zweiten connect() Aufruf tu ich eigentlich das selbe. Anschließend wird der Fortschrittsbalken mittels show() angezeigt und die Kopieraktion mit start() ausgeführt.
Was mach ich falsch? Der Fehler tritt echt zu selten auf um ihn mit dem Debugger zu finden und bisher trat er nur dann auf, wenn die Operation so schnell über die Bühne lief, sodass der Ladebalken nichtmal gemalen wurde.
-
Ich seh hier jetzt noch keinen Fehler. Interessant wäre vielleicht, mit was processed verbunden wird.
Setz einfach einen Breakpoint an der Stelle, wo der Fehler ausgegeben wird. Das mit dem recursive repaint war glaub ich direkt in qpainter.cpp.
-
Mechanics schrieb:
Ich seh hier jetzt noch keinen Fehler. Interessant wäre vielleicht, mit was processed verbunden wird.
void file_operation_widget::set_connections(){ connect(operation, &QThread::finished, this, &QWidget::close); connect(operation, &file_operation::message, label, &QLabel::setText); connect(operation, &file_operation::error, this, &file_operation_widget::show_error); connect(operation, &file_operation::processed, [this]{ bar->setValue(bar->value() + 1); QApplication::processEvents(); }); ...
QApplication::processEvents() benutze ich, weil das Fenster bei großen Operationen manchmal nicht vollständig gemalen wird.
Setz einfach einen Breakpoint an der Stelle, wo der Fehler ausgegeben wird.
Ich weiß nicht wirklich wo das sein soll. Der Fehler taucht sagen wir jedes Schaltjahr mal auf und zwar mitten in der Operation und bisher auch nur, wenn die Operation so klein war, dass das Fenster nichtmal gemalen wird. Die Daten werden also nicht vollständig kopiert.
-
Das processEvents hast du grad zum ersten mal erwähnt. Das kann grundsätzlich natürlich schon zu rekursiven Aufrufen führen.
Ich hab grad keine Lust, mich in dein konkretes Problem reinzudenken, aber grundsätzlich ist es so, dass ein Paint Event noch in der Event Queue ist, du rufst Process Events auf, und dasselbe Paint Event wird wieder abgearbeitet. Ich hab keine Ahnung, wie das bei Qt 5+ ist, aber bei Qt 4 ist es zumindest problematisch, wenn nicht ein harter Crash.
-
Mechanics schrieb:
Das processEvents hast du grad zum ersten mal erwähnt. Das kann grundsätzlich natürlich schon zu rekursiven Aufrufen führen.
Ich hab grad keine Lust, mich in dein konkretes Problem reinzudenken, aber grundsätzlich ist es so, dass ein Paint Event noch in der Event Queue ist, du rufst Process Events auf, und dasselbe Paint Event wird wieder abgearbeitet. Ich hab keine Ahnung, wie das bei Qt 5+ ist, aber bei Qt 4 ist es zumindest problematisch, wenn nicht ein harter Crash.Achso, naja, die Doku von Qt sagt jedenfalls, dass processEvents() thread-safe ist.
Ich werds mal raushauen und schauen, ob der Fehler nochmals auftritt. Wenn er nicht mehr auftritt, dann wars das und danke dir.
-
Nein, das wars nicht, habe eben einen Segfault bekommen.
Was für ein mieser Zufall.
-
Beim segfault müsstest du aber den Callstack sehen.
-
Der Debugger springt sofort in eine Asm-Datei, davon weiß ich leider nichts. Im Stack steht folgendes:
1 QPainter::drawPixmap(QPointF const&, QPixmap const&) 0x7ffff651218b 2 ?? 0x7ffff6b68b1b 3 ?? 0x7ffff6b5b766 4 QPushButton::paintEvent(QPaintEvent *) 0x7ffff6c1c0ee 5 QWidget::event(QEvent *) 0x7ffff6acf298 6 QApplicationPrivate::notify_helper(QObject *, QEvent *) 0x7ffff6a8a7bc 7 QApplication::notify(QObject *, QEvent *) 0x7ffff6a8f95f 8 QCoreApplication::notifyInternal2(QObject *, QEvent *) 0x7ffff5403280 9 QWidgetPrivate::sendPaintEvent(QRegion const&) 0x7ffff6ac81ba 10 QWidgetPrivate::drawWidget(QPaintDevice *, QRegion const&, QPoint const&, int, QPainter *, QWidgetBackingStore *) 0x7ffff6ac8809 11 QWidgetPrivate::paintSiblingsRecursive(QPaintDevice *, QList<QObject *> const&, int, QRegion const&, QPoint const&, int, QPainter *, QWidgetBackingStore *) 0x7ffff6ac94fc 12 QWidgetPrivate::drawWidget(QPaintDevice *, QRegion const&, QPoint const&, int, QPainter *, QWidgetBackingStore *) 0x7ffff6ac8374 13 ?? 0x7ffff6a987bb 14 ?? 0x7ffff6a989a9 15 QWidgetPrivate::syncBackingStore() 0x7ffff6ab6f6f 16 QWidget::event(QEvent *) 0x7ffff6acf368 17 QApplicationPrivate::notify_helper(QObject *, QEvent *) 0x7ffff6a8a7bc 18 QApplication::notify(QObject *, QEvent *) 0x7ffff6a8f95f 19 QCoreApplication::notifyInternal2(QObject *, QEvent *) 0x7ffff5403280 20 QCoreApplicationPrivate::sendPostedEvents(QObject *, int, QThreadData *) 0x7ffff54051fc ... <More>
processEvents() hab ich übrigens rausgehauen.
-
Ich seh da soweit nichts großartig verdächtiges. Ist es der Hauptthread, was machen die anderen Threads grad? Kannst du an den Variablen usw. erkennen, was schiefgeht?
-
Mechanics schrieb:
Ich seh da soweit nichts großartig verdächtiges. Ist es der Hauptthread, was machen die anderen Threads grad? Kannst du an den Variablen usw. erkennen, was schiefgeht?
Das "Locals and Expressions"-Fenster ist leer, wenn der Debugger in die ASM-Zeile springt. Ich glaube es liegt am Mainthread, da ich wirklich keine QObject-Methoden vom Thread aus aufrufe. Wann kann ein Recursive Paint denn sonst noch auftreten? Echt nur wenn man in einem anderen Thread Objekte malt? Das ist bei mir nämlich nicht der Fall, in den Threads benutze ich ausschließlich nur Signals.
Hier nochmal der Callstack:
1 QTransform::type() const 0x7ffff6563080 2 QRasterPaintEngine::drawImage(QPointF const&, QImage const&) 0x7ffff64f4f15 3 QRasterPaintEngine::drawPixmap(QPointF const&, QPixmap const&) 0x7ffff64f9e4e 4 QPainter::drawPixmap(QPointF const&, QPixmap const&) 0x7ffff651214b 5 ?? 0x7ffff6b66cb5 6 ?? 0x7ffff6b5a8d5 7 QCommonStyle::drawControl(QStyle::ControlElement, QStyleOption const *, QPainter *, QWidget const *) const 0x7ffff6b05551 8 ?? 0x7ffff6b5b2e5 9 QProgressBar::paintEvent(QPaintEvent *) 0x7ffff6c1b1b0 10 QWidget::event(QEvent *) 0x7ffff6acf298 11 QProgressBar::event(QEvent *) 0x7ffff6c1bcee 12 QApplicationPrivate::notify_helper(QObject *, QEvent *) 0x7ffff6a8a7bc 13 QApplication::notify(QObject *, QEvent *) 0x7ffff6a8f95f 14 QCoreApplication::notifyInternal2(QObject *, QEvent *) 0x7ffff5403280 15 QWidgetPrivate::sendPaintEvent(QRegion const&) 0x7ffff6ac81ba 16 QWidgetPrivate::drawWidget(QPaintDevice *, QRegion const&, QPoint const&, int, QPainter *, QWidgetBackingStore *) 0x7ffff6ac8809 17 QWidgetPrivate::paintSiblingsRecursive(QPaintDevice *, QList<QObject *> const&, int, QRegion const&, QPoint const&, int, QPainter *, QWidgetBackingStore *) 0x7ffff6ac94fc 18 QWidgetPrivate::drawWidget(QPaintDevice *, QRegion const&, QPoint const&, int, QPainter *, QWidgetBackingStore *) 0x7ffff6ac8374 19 ?? 0x7ffff6a987bb 20 ?? 0x7ffff6a989a9 ... <More>
-
Ich habe mal testhalber Qt::BlockedQueuedConnection ausprobiert und der Fehler scheint bisher nicht mehr aufzutreten. Ich melde mich dann mal wieder, falls der Fehler wieder auftaucht.
-
Qtnase schrieb:
Wann kann ein Recursive Paint denn sonst noch auftreten? Echt nur wenn man in einem anderen Thread Objekte malt?
Nein, hatte ich ja schon geschrieben. Ich kenn mich mit Qt5 übrigens nicht explizit aus, aber es würde mich schon stark überraschen, wenn man aus mehreren Threads malen könnte. Klar, du kannst in einem Seitenthread einen QPainter erstellen und in ein QImage malen (aber nicht QPixmap in Qt4), aber aus mehreren Threads in denselben QPainter zeichnen macht keinen Sinn.
BlockedQueuedConnection... Das ist jetzt nicht wirklich dafür gedacht, könnte aber schon Sinn machen, dass es hier damit funktioniert. Ich hätt aber eigentlich schon erwartet, einen "sauberen" Callstack zu sehen, bei dem ersichtlich ist, dass der Aufruf tatsächlich rekursiv ist. Wenn man das Problem nicht genau versteht, kann es sein, das man das jetzt kurzfristig löst, später aber noch mehr Probleme bekommt.
-
Das mit dem recursive paint event geht auch, indem man selbst paintEvent() überschreibt und dort indirekt einen neuen QPainter aufmacht, z.B. indem man einen QDialog öffnet (vorzüglich demonstriert das Jürgen Wolf in seinem Qt4-Programmieren-Buch - nicht nachmachen ;)).
Implementierst du denn irgendwo paintEvent selber?
-
Cremant schrieb:
Implementierst du denn irgendwo paintEvent selber?
Nope.
Also seitdem ich die Verbindungen mit Qt::BlockingQueuedConnection gemacht habe, trat der Fehler nicht mehr auf.