Threading scene
-
Trying to use one thread to update scene, and another thread to show, but sigfaults if scene id updated to often.
#include "CThreadController.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); CThreadController threcon; MainWindow w; CSceneView scene; CViewLoop vloop; w.SetController(&threcon); scene.SetController(&threcon); threcon.SetMainWindow(&w); threcon.SetSceneView(&scene); threcon.SetViewLoop(&vloop); threcon.startThreads(); threcon.ShowPointers(); w.show(); return a.exec(); }
#include "CMainWindow.h" #include "CThreadController.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , m_ui(new Ui::MainWindow) { m_ui->setupUi(this); } MainWindow::~MainWindow() { delete m_ui; } void MainWindow::SetController(CThreadController *controller) { m_threadcontroller = controller; connect(m_ui->pushButton, &QPushButton::clicked,this, &MainWindow::Run); } void MainWindow::ReceiveGView(QGraphicsScene *scene) { std::cout << "================ MainWindow::ReceiveGView ================ \n" << std::flush; std::cout << "scene = " << scene << '\n' << std::flush; m_ui->graphicsView->setScene(scene); m_ui->graphicsView->show(); // want to delete scene here, to save memory //scene->clear(); //causes crash [not supprised] } void MainWindow::ReceiveGView2(QGraphicsScene *scene) { std::cout << "================ MainWindow::ReceiveGView2 ================ \n" << std::flush; m_ui->graphicsView_2->setScene(scene); m_ui->graphicsView_2->show(); } void MainWindow::Run() { std::cout << "================ MainWindow::Run ================ \n" << std::flush; emit EmitDisPlayView(); }
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QApplication> #include <QCoreApplication> #include <QDebug> #include <QGraphicsScene> #include <QtGui> #include <QMainWindow> //#include <QSerialPort> #include <QThread> #include <cstring> #include <fstream> #include <functional> #include <iostream> #include <sstream> #include <string> #include "ui_mainwindow.h" QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class CThreadController; class MainWindow : public QMainWindow { Q_OBJECT private: Ui::MainWindow *m_ui; CThreadController *m_threadcontroller; public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); void SetController(CThreadController *controller); void ReceiveGView(QGraphicsScene *scene); void ReceiveGView2(QGraphicsScene *scene); void Run(); // signals signals: void EmitDisPlayView(); }; #endif // MAINWINDOW_H
#include "CSceneView.h" CSceneView::CSceneView() : QGraphicsScene() { m_GScene = new QGraphicsScene(this); m_GScene_2 = new QGraphicsScene(this); } // get/set void CSceneView::SetController(CThreadController *controller) { std::cout << "================ CSceneView::SetController ================ \n" << std::flush; m_threadcontroller = controller; } void CSceneView::DisPlayView() { std::cout << "================ CSceneView::DisPlayView ================ \n" << std::flush; /* m_GScene->clear(); m_GScene_2->clear(); m_GScene = new QGraphicsScene(this); m_GScene_2 = new QGraphicsScene(this); */ int i = 0; int j = 0; while(i < 20000) { while(j < 20000) { m_GScene->addEllipse((j), (i), 1, 1); m_GScene_2->addEllipse((i), (j), 1, 1); j++; } i++; } emit EmitGView(m_GScene); emit EmitGView2(m_GScene_2); } void CSceneView::ReceiveDisPlayView() { std::cout << "================ CSceneView::ReceiveDisPlayView ================ \n" << std::flush; QString s = QString::number((long long)(this->thread()), 16); std::cout << "CSceneView this->thread() = " << s.toStdString() << '\n' << std::flush; // new scene so two threads are not using same pointer at same time m_GScene->clear(); m_GScene_2->clear(); //m_GScene = new QGraphicsScene(this); //m_GScene_2 = new QGraphicsScene(this); DisPlayView(); //TestArray(); }
#ifndef CSCENEVIEW_H #define CSCENEVIEW_H #include <QGraphicsScene> #include <QObject> #include <iostream> class CThreadController; class CSceneView : public QGraphicsScene { Q_OBJECT // data public: CThreadController *m_threadcontroller; QGraphicsScene * m_GScene; QGraphicsScene * m_GScene_2; // constructor CSceneView(); // get/set void SetController(CThreadController *controller); // function void DisPlayView(); // signals signals: void EmitGView(QGraphicsScene *scene); void EmitGView2(QGraphicsScene *scene); void EmitDisPlayView(); public slots: void ReceiveDisPlayView(); }; #endif // CSCENEVIEW_H
#include "CThreadController.h" CThreadController::CThreadController() : QThread() { m_threadsceneview = new QThread(); } void CThreadController::SetMainWindow(MainWindow *w) //?? : public QMainWindow { std::cout << "CThreadController w = " << w << '\n' << std::flush; m_mainwindow = w; } void CThreadController::SetSceneView(CSceneView *sceneview) //?? : public QGraphicsScene { std::cout << "CThreadController sceneview = " << sceneview << '\n' << std::flush; m_sceneview = sceneview; m_sceneview->moveToThread(m_threadsceneview); connect(m_threadsceneview, &QThread::finished, m_sceneview, &QObject::deleteLater); connect(m_threadsceneview, &QThread::finished, m_threadsceneview, &QThread::deleteLater); m_threadsceneview->start(); } void CThreadController::SetViewLoop(CViewLoop *vloop) //?? : public QThread { std::cout << "CThreadController vloop = " << vloop << '\n' << std::flush; std::cout << "CThreadController m_threadvloop = " << m_threadvloop << '\n' << std::flush; m_threadvloop = vloop; connect(m_threadvloop, &QThread::finished, m_threadvloop, &QObject::deleteLater); connect(m_threadvloop, &QThread::finished, m_threadvloop, &QThread::deleteLater); m_threadvloop->start(); } // function void CThreadController::startThreads() { std::cout << "CThreadController::startThreads begin\n" << std::flush; std::cout << "CThreadController::startThreads m_mainwindow = " << m_mainwindow << '\n' << std::flush; std::cout << "CThreadController::startThreads m_sceneview = " << m_sceneview << '\n' << std::flush; //?? CSeneView -> mainwindow connect(m_sceneview, &CSceneView::EmitGView, m_mainwindow, &MainWindow::ReceiveGView); connect(m_sceneview, &CSceneView::EmitGView2, m_mainwindow, &MainWindow::ReceiveGView2); //?? MainWindow -> CSeneView connect(m_mainwindow, &MainWindow::EmitDisPlayView, m_sceneview, &CSceneView::ReceiveDisPlayView); //?? CSeneView -> CSeneView connect(m_sceneview, &CSceneView::EmitDisPlayView, m_sceneview, &CSceneView::ReceiveDisPlayView); //?? CViewLoop -> CSeneView connect(m_threadvloop, &CViewLoop::EmitDisPlayView, m_sceneview, &CSceneView::ReceiveDisPlayView); } void CThreadController::ShowPointers() { QString qs = QString::number((long long)(QThread::currentThreadId()), 16); std::cout << "CThreadController QThread::currentThreadId() = " << qs.toStdString() << '\n' << std::flush; std::cout << "CThreadController::ShowPointers m_threadvloop = " << m_threadvloop << '\n' << std::flush; std::cout << "CThreadController::ShowPointers m_mainwindow = " << m_mainwindow << '\n' << std::flush; std::cout << "CThreadController::ShowPointers m_sceneview = " << m_sceneview << '\n' << std::flush; }
#ifndef CTHREADCONTROLLER_H #define CTHREADCONTROLLER_H #include "CMainWindow.h" #include "CSceneView.h" #include "CViewLoop.h" #include <QObject> #include <QThread> class CThreadController : public QThread { Q_OBJECT private: // data MainWindow *m_mainwindow; //?? : public QMainWindow CSceneView *m_sceneview; //?? : public QGraphicsScene QThread* m_threadsceneview; CViewLoop *m_threadvloop; //?? : public QThread public: CThreadController(); // get/set void SetMainWindow(MainWindow *w); void SetSceneView(CSceneView *sceneview); void SetViewLoop(CViewLoop *vloop); // function void startThreads(); void ShowPointers(); }; #endif // CTHREADCONTROLLER_H
//#include "CSceneView.h" #include "CViewLoop.h" CViewLoop::CViewLoop() { m_sState = "error"; } // get/set void CViewLoop::SetState(std::string s) { m_sState = s; } // function void CViewLoop::Print(std::string s) { std::cout << s << '\n' << std::flush; m_file << s << '\n' << std::flush; } void CViewLoop::run() { /* ... here is the expensive or blocking operation ... */ int runtime = 10; int i = 0; while(true) { if( (m_sState == "error") || (m_sState == "run")) { Print("CViewLoop::run 'error' or 'run' i = " + std::to_string(i)); emit EmitDisPlayView(); sleep(runtime); //?? 1 = 1 seconds, 5 = 5 seconds i++; } else { Print("CViewLoop::run 'else'"); this->sleep(40); } } Print("CViewLoop::run exit"); }
#ifndef CVIEWLOOP_H #define CVIEWLOOP_H #include <fstream> #include <iostream> #include <QObject> #include <QThread> //class CSceneView; class CViewLoop : public QThread { Q_OBJECT //?? data private: std::ofstream m_file; std::string m_sState; //CSceneView *m_scene; public: // constructors CViewLoop(); // get/set void SetState(std::string s); //void SetSceneView(CSceneView *scene); // functions void Print(std::string s); void run() override; // signals signals: void EmitDisPlayView(); }; #endif // CVIEWLOOP_H
if
m_GScene = new QGraphicsScene(this);
is only in constructor, then it will run , but sigfaults or locks up dependent on how often a button and which button is clicked.
if it is used each time scene is updated, randomly sigfaults, around 2 to 20 updates.
i moved it between two functions, think one function lasted longer that the other, but could not figure out how, then i ran same program multiple times, and noticed that sigfault was random.
It seems updating scene at same time as scene would be a cause of sigfault, so I made new QGraphicsScene(this); each time. I tried to pass a copy, but seems copy can not be made of scene, could not even write a copy constructor for it.How to update scene in one thread and and shown on another thread?
-
@micha_eleric
Qt requires that all UI updates only happen on the main thread. Secondary threads must not do UI stuff. They can do calculations (not involving UI) and communicate with the UI thread via signals/slots as required.On another matter, you cannot copy a
QObject
(defined to forbid copy constructor) or anything derived from it, which includes aQGraphicsScene
, so no point wasting time on that. -
@micha_eleric Is there a further question here?
-
@micha_eleric as mentioned by @JonB, i am afraid you cannot do drawEclipse in worker thread, refer here : https://doc.qt.io/qt-6/thread-basics.html#qobject-and-threads
only certain QPaintDevices which do not inherit QObject can be worked on in a Worker Thread i.e. QImage.
when you run your example you might get something like below
QCoreApplication::sendPostedEvents: Cannot send posted events for objects in another thread
this is indicating the above point of why this is not feasible.
also try looking to parameterized constructors, instead of setter methods. and try to limit connect calls to constructor of the class. latter suggestion is just for readability purpose.
-
You must make sure that you do not change the scene while it is being drawn. Otherwise the change might change a pointer (and delete the underlying object) which the GUI thread is currently accessing. This will definitely blow up.
The best advice is to do some sort of double buffering: Use 2 different pointers to QGraphicsScene. Use one pointer to change the scene and the other to display inside the GUI. The GUI thread needs to lock a mutex while drawing (and unlock it afterwards). After doing your changes in the other thread, also lock the mutex and swap the two pointers. Now, you are thread safe.
-
@micha_eleric said in Threading scene:
@JonB thanks. issue i have been having, is while scene is updating, gui freezes.
you're drawing/updating 400 000 000 - 400 million! - objects on a CPU based renderer and asking yourself why the guy freezes ?
-
@micha_eleric
I had not noticed, but as @J-Hilk has said 400,000,000 is a lot of items to draw ;-) Not to mention, this is a lot for the user to look at/I'm not sure there are even that many pixels on the screen! But I guess you will see this is just a test.If you have a "lot" of add/move/delete operations to perform, you must either (a) do the operations in batches on a timer from a "queue" (at least conceptually), presumably from the UI thread, so that the UI has time to render them and remains responsive, or (b) if @SimonSchroeder's principle of "double-buffering"/mutexing works from a thread you would have to release the mutex periodically after batches performed so that the UI thread gets some time to process the updates without blocking on a mutex for too long.