mercredi 15 juin 2016

Multithreading with PySide : is my structure viable?

I'm running into a very strange error while using QThread in PySide. As I run my code, when I click on the button that is supposed to launch the thread and send Signal, I get :

AttributeError: 'builtin_function_or_method' object has no attribute 'displayText'

at line :

self.displayMessage.connect(self.window.displayText)

NB : displayText is a method that I defined in my MainWindow(QWidget) class, while displayMessage is the signal to be emitted.

This error seems, according to the Internet, to occur in various situations, and I couldn't find one that suits my case yet. Therefore I have 2 questions :

  • Have you guys ever met this error before in a similar situation, and could you (if yes) give me some tips ?
  • Am I doing it right ? I can't post my code directly down here, so I created a short verifiable example in which I used the same process. Unfortunately, it seems to work perfectly. Please tell me if at least my construction is correct.

Thank you very much.


EDIT : I eventually figured out that I have forgotten a self somewhere in my big code, but the actual error was hidden to me behind this one that I didn't know. I'm still interested in whether or not this code is reliable, and am fully open to suggestions of improvement.


Without thread

When you click "GO !" and try to move the window, you can see that it freezes for a second.

#!/usr/bin/env python
# -*- encoding : utf-8 -*-
import sys
import time
from PySide.QtCore import *
from PySide.QtGui import *

class MainWindow(QWidget):
    def __init__(self, qt_app):
        QWidget.__init__(self)
        worker = Worker(self)
        self.qt_app = qt_app
        self.setGeometry(100, 100, 220, 40)
        self.infoPanel = QTextEdit(self)
        self.infoPanel.setReadOnly(True)
        self.goButton = QPushButton('GO !', self)
        self.goButton.clicked.connect(lambda: worker.displayOnWindow())
        self.layout = QVBoxLayout()
        self.layout.addWidget(self.infoPanel)
        self.layout.addWidget(self.goButton)
        self.setLayout(self.layout)
    def launch(self):
        self.show()
        self.qt_app.exec_()

class Worker():
    def __init__(self, window):
        self.counter = 0
        self.window = window
    def displayOnWindow(self):
        time.sleep(1)
        self.window.infoPanel.append(str(self.counter))
        self.counter += 1

if __name__=='__main__':
    qt_app = QApplication(sys.argv)
    mw = MainWindow(qt_app)
    mw.launch()

With thread

Now you can move the window without trouble, since the sleeping thread is not the one that displays the window. I've written a sub-class for my thread because in the original code there are some functions that will be called by several buttons.

#!/usr/bin/env python
# -*- encoding : utf-8 -*-
import sys
import time
from PySide.QtCore import *
from PySide.QtGui import *

class MainWindow(QWidget):
    def __init__(self, qt_app):
        QWidget.__init__(self)
        worker = SubWorker(self)
        self.qt_app = qt_app
        self.setGeometry(100, 100, 220, 40)
        self.infoPanel = QTextEdit(self)
        self.infoPanel.setReadOnly(True)
        self.goButton = QPushButton('GO !', self)
        self.goButton.clicked.connect(lambda: worker.start())
        self.layout = QVBoxLayout()
        self.layout.addWidget(self.infoPanel)
        self.layout.addWidget(self.goButton)
        self.setLayout(self.layout)
    def launch(self):
        self.show()
        self.qt_app.exec_()
    @Slot(int)
    def displayOnWindow(self, i):
        self.infoPanel.append(str(i))

class Worker(QThread):
    displayMessage = Signal(int)
    def __init__(self, window):
        QThread.__init__(self)
        self.counter = 0
        self.window = window
        self.displayMessage.connect(self.window.displayOnWindow)
    def run(self, *args, **kwargs):
        return QThread.run(self, *args, **kwargs)

class SubWorker(Worker):
    def __init__(self, window):
        Worker.__init__(self, window)
    def run(self):
        time.sleep(1)
        self.displayMessage.emit(self.counter)
        self.counter += 1

if __name__=='__main__':
    qt_app = QApplication(sys.argv)
    mw = MainWindow(qt_app)
    mw.launch()

Aucun commentaire:

Enregistrer un commentaire