taurus-org / taurus

Moved to https://gitlab.com/taurus-org/taurus
http://taurus-scada.org
43 stars 46 forks source link

setModel and setText of command in QThread #1104

Closed MikeFalowski closed 4 years ago

MikeFalowski commented 4 years ago

Hi everyone,

I tried to move setting model of Tango attributes and commands from the main thread of Qt to QThread since it was taking a lot of time to start and show the application.

The problem occurred when I was setting text for buttons. If I use setText before setModel, setting model will reset the text back to the Tango command name, so I moved setText from setup_layouts method to set_models placing it just after setModel.

Unfortunately in QThread it happens to work only sometimes, some buttons have text I have set and some not. Adding code between those two methods (like sleep or even prints) help in this matter and if code between takes longer to execute, it helps in more cases (sleep actually solved the entire problem).

It looks to me like it's because some signal from setModel, which is setting text, is received just after setModel is finished and if it's too late it is called after fnishing setText.

This is the example snippet:

devices = ["sys/tg_test/1", "sys/tg_test/2", "sys/tg_test/3"]

class MyWidget(QtGui.QWidget):
    def __init__(self):
        super(MyWidget, self).__init__()
        self.setup_layouts
        self.setmodels_thread= SetModelsThread(self)
        self.setmodels_thread.start()

    def set_models(self):
        for i, device in enumerate(devices):
            self.close_button[i].setModel(device)
            self.close_button[i].setText("Close")

            self.open_button[i].setModel(device)
            self.open_button[i].setText("Open")

    def setup_layouts(self):
        # setup layouts and widgets

app = TaurusApplication([''])
window = MyWidget()
window .show()

sys.exit(app.exec_())

Thread was defined in this way:

class SetModelsThread(QtCore.QThread):

    def __init__(self, widget):
        QtCore.QThread.__init__(self)
        self.widget = widget

    def run(self):
        self.widget.set_models()

I'm not sure if it's the best way to handle setting models.

In https://github.com/taurus-org/taurus/issues/1092#issuecomment-601641719, @sergirubio mentioned that in ALBA some GUI's are now using subscription in WorkerThread. I guess that subscription takes most of the time when setting model so if we would do the same, could it help in this matter?

Thanks for help, Mike

cpascual commented 4 years ago

Just a quick note: for some widgets the call to setModel() will end up triggering some GUI method, and this is not safe when done outside of the main Qt thread:

the GUI classes, notably QWidget and all its subclasses, are not reentrant. They can only be used from the main thread (...) In practice, the impossibility of using GUI classes in other threads than the main thread can easily be worked around by putting time-consuming operations in a separate worker thread and displaying the results on screen in the main thread when the worker thread is finished.

MikeFalowski commented 4 years ago

Thanks @cpascual for fast reply. I removed QThread as you advised and I'm setting models in main thread now. I'll try to figure it out how to move only subscription from the main thread, but I guess this won't be easy,