Heavy operations in a second thread
-
I decided to implement multithreading in my project so as not to block the UI thread. I got a little confused.
My project has a main class that contains and manages all other classes - BasicMainWindow. The main subclass is Model. It contains information about nodes and elements (Finite Element model if that makes things clear to you. Or like a set of vertices and elements in Blender)Iteration 1:
I'm creating a subclass called ModelParser that can only read nodes from a txt. I put it in the second thread (following the example https://doc.qt.io/qt-5/qthread.html), where it parses a text file and creates a model. It has the 'send_node_data' signal connected to the 'receive_node_data' slot of the Model class in the main thread. Everything worked, the data was read, QProgressBar, located in BMW, reported progress.Iteration 2:
I decided to pass a pointer to the Model object to the ModelParser class in order to call object creation methods directly, without signals / slots.
ModelParser(Model* model);I'm not sure if this is correct, but it will allow me to NOT create dozens of signals/slots, but use the old model->add_node(...);
Problem: The functionality of the ModelParser test class is severely limited. He cannot do everything that BMW could.Iteration 3:
I am passing a pointer to a BMW object to the ModelParser.
ModelParser(BasicMainWindow* bmw);
bmw->do_stuff(data_from_parser);ModelParser reads the text file and calls the methods already on the BMW object. Everything works, but intuitively, it seems to me that this is a bad architecture. I am also confused by the access of all at once to the pointer to the Model object.
I want to understand how to properly organize all this in the Qt style using the example of reading a model from a text file, so that later I can apply this to the ModelViewer class (QOpenGLWidget), which creates an interactive graphical model. When large model meshes are recreated - UI thread freezes...
Kirill
-
Hi,
Iteration 1 is the correct way.
Do you really need to load the whole model in one shot ?
Do you need to have the whole data available at all time ?
Based on that you might be able to load data in a smart way rather than everything in memory all the time. -
@SGaist Hey, thanks for your reply
Iteration 1 is the correct way.
Then I will create all the signals. I am just afraid to put extra signals where I could use a direct call.
Do you really need to load the whole model in one shot ?
Yes, it is like 'File -> Open...' in other applications. I am sure it should read all data.
Do you need to have the whole data available at all time ?
Even a huge structural model (Finite Element Model) without its graphical representation is not more than 1GB of RAM. With the graphics though it could be about 12 GB in my case. However, I try to cache as much as I can because the whole point of my applcation is to Create/Open/Import -> Edit with the maximum pleasure in terms of UI response time and number of mouse clicks -> Export to some application with a FE solver. In other words, I am making a preprocessor.
Based on that you might be able to load data in a smart way rather than everything in memory all the time.
It is more about the last part of my question. I indeed need to isolate from time to time a part of the model. Now I do it in the UI thread, which leads to freezes (up to seconds on big models). But I do not want to start my multithreading adventure from OpenGL because the GL stuff itself a bit tricky for me sometimes.
-
You can also create the whole model in the second thread and when it finished set this model to your view in the main thread.
-
@Christian-Ehrlicher Thank you, it is an interesting approach. You mean the Model will be created (and live?) in the second thread and just send signals to GLWidget in the UI thread like create_point, create_line... ?
-
@goloviznin-k said in Heavy operations in a second thread:
created (and live?)
created yes, live no - the second thread ends when you finished parsing and the model is passed to the main thread. Don't give the model a parent as long as it is in the second thread then you don't have a problem with moving it to the main thread later on.
-
@Christian-Ehrlicher My second thread starts with application start and doesn't die. I wanted to put workers like Parser and Merger (the guy who can merge duplicate nodes - it is a heavy task) there so that they do the hard work at the right time without interfering with the UI. Is this a wrong approach?
-
When you modify the model later on in the second thread then my approach will not work. Though only the loading takes long.
-
@Christian-Ehrlicher Ok, let's say the Model lives in the main thread. What if I then I changed it and want to perform a heavy operation, for example, merge nodes (merge closely located nodes and notify the elements which are related to them).
What are my moves in terms of threads? -
@goloviznin-k said in Heavy operations in a second thread:
What are my moves in terms of threads?
At least not easy. I even would say - very hard when you move around stuff. You have to pass all information to fullfil the requirements for the begin/end - Functions with a signal/slot connection. E.g. when you want to remove some rows, you have to emit a signal 'removeRowInModel(const QVector<ids> &idsToRemove)'. Moving and adding will be much more problematic.
-
@Christian-Ehrlicher God, I just realized that maybe you think I'm using the Model View ViewModel (or something like that) approach. I just heard that it exists. I don't do anything like that. I call it Model because it is a structural FE model :)
-
Ok, then what do you do with this model in your UI? How do changes in the second thread reflect in the main thread?
-
@Christian-Ehrlicher Now (it is Iteration 3 from the 1rst post) a SecondThreadManager which lives while the App lives just directly calls methods on the pointer to the model. I understand it is not right, I want to fix it and I am trying to figure out how to do it in a right and Qt way. When I understand it, I will try to handle OpenGL and other stuff in the second thread.