QTimer на Qthread - PullRequest
       72

QTimer на Qthread

0 голосов
/ 27 августа 2018

У меня есть графический интерфейс, который мне нужно постоянно обновлять с помощью Qtimer, для этого я использую рабочий Qthread, это мой код:

from PyQt5.QtWidgets import QApplication, QPushButton, QWidget
from PyQt5.QtCore import QThread, QTimer
import sys
import threading


class WorkerThread(QThread):
    def run(self):
        print("thread started from :" + str(threading.get_ident()))
        timer = QTimer(self)
        timer.timeout.connect(self.work)
        timer.start(5000)
        self.exec_()

    def work(self):
        print("working from :" + str(threading.get_ident()))
        QThread.sleep(5)


class MyGui(QWidget):

    worker = WorkerThread()

    def __init__(self):
        super().__init__()
        self.initUi()
        print("Starting worker from :" + str(threading.get_ident()))
        self.worker.start()

    def initUi(self):
        self.setGeometry(500, 500, 300, 300)
        self.pb = QPushButton("Button", self)
        self.pb.move(50, 50)
        self.show()


app = QApplication(sys.argv)
gui = MyGui()
app.exec_()

вывод:

Starting worker from :824
thread started from :5916
working from :824
working from :824

таймер работает в главном потоке, который заморозил мой графический интерфейс, как я могу это исправить?

Ответы [ 3 ]

0 голосов
/ 27 августа 2018

Попробуйте:

import sys
import threading
from PyQt5.QtGui     import *
from PyQt5.QtCore    import *
from PyQt5.QtWidgets import *

class WorkerThread(QThread):

    workSignal = pyqtSignal(str)

    def run(self):
        print("thread started from :" + str(threading.get_ident()))
        textLabel = "thread started from :" + str(threading.get_ident())
        self.workSignal.emit(textLabel)
        self.work()

    def work(self):
        print("working from :" + str(threading.get_ident()))
        textLabel = "working from :" + str(threading.get_ident())
        self.workSignal.emit(textLabel)


class MyGui(QWidget):

    worker = WorkerThread()

    def __init__(self):
        super().__init__()
        self.initUi()
        print("Starting worker from :" + str(threading.get_ident()))
        self.lbl.setText("Starting worker from :" + str(threading.get_ident()))

        self.worker.workSignal.connect(self.showLabel)

    def initUi(self):
        self.setGeometry(700, 350, 300, 150)

        self.lcdTime = QLCDNumber(self)
        self.lcdTime.setSegmentStyle(QLCDNumber.Filled)   # Outline Filled Flat
        self.lcdTime.setDigitCount(8)    

        self.timer = QTimer(self)
        self.lbl   = QLabel(self) 
        self.pb = QPushButton("Button Close", self, clicked=self.close)

        vbox = QVBoxLayout()
        vbox.addWidget(self.lcdTime)
        vbox.addWidget(self.lbl)
        vbox.addWidget(self.pb)
        self.setLayout(vbox)

        self.timer.timeout.connect(self.showTime)
        self.timer.start(1000)        
        self.numSec = 0
        self.show()

    def showTime(self):
        time = QTime.currentTime()
        text = time.toString("hh:mm:ss")           
        if ((time.second() % 2) == 0):
            text = text[0:2] + ' ' + text[3:5] + ' ' + text[6:]
        self.lcdTime.display(text)
        self.numSec += 1
        if self.numSec == 5:
            self.worker.start()
            self.numSec = 0

    def showLabel(self, textLabel):
        self.lbl.setText(textLabel)

app = QApplication(sys.argv)
gui = MyGui()
app.exec_()

enter image description here

0 голосов
/ 28 августа 2018

Ответ: в вашем случае я не вижу необходимости использовать QThread.

TL;DR;

Когда мне нужно использовать другой поток в контексте графического интерфейса?

Только один поток должен использоваться, когда какая-то задача может заблокировать основной поток, называемыйпоток GUI, и блокировка вызвана, потому что задача занимает много времени, препятствуя тому, чтобы цикл событий GUI нормально выполнял свою работу.Все современные графические интерфейсы выполняются в EventLop, что позволяет получать уведомления от ОС, такие как клавиатура, мышь и т. Д., А также позволяет изменять состояние графического интерфейса в зависимости от пользователя.

ВВ вашем случае я не вижу какой-либо тяжелой задачи, поэтому я не вижу необходимости в QThread, я действительно не знаю, что это за задача, которую вы хотите периодически запускать.

Предполагая, что у вас есть задача, которая потребляетмного времени, скажем 30 секунд, и вы должны делать это каждые полчаса, тогда нить необходима.и в вашем случае вы хотите использовать для него QTimer.

Давайте разберёмся по частям, QTimer - это класс, который наследуется от QObject, а QObject принадлежит к тому же, что и родительский.и если у него нет родителя, он принадлежит потоку, в котором он был создан.С другой стороны, часто думают, что QThread - это поток в Qt , но это не так, QThread - это класс, который позволяет обрабатывать жизненный цикл собственного потока, иэто ясно указано в документах : Класс QThread предоставляет независимый от платформы способ управления потоками .

Зная выше, давайтепроанализируйте ваш код:

timer = QTimer(self)

В вышеприведенном коде self является родителем QTimer, а self является QThread, поэтому QTimer принадлежит потоку родителя QThread или где QThread былсоздано, а не потоку, который обрабатывает QThread.

Тогда давайте посмотрим код, в котором было создано QThread:

worker = WorkerThread()

Как мы видим, QThread не имеет родителя, тогдаQThread принадлежит потоку, в котором он был создан, то есть QThread принадлежит основному потоку, и, следовательно, его QTimer дочерний элемент также принадлежит основному потоку.Также обратите внимание, что новый поток, который обрабатывает QThread, имеет только область действия метода run(), если метод находится в другом месте, относится к полю, где был создан QThread, со всем вышеперечисленным мы видим, что вывод кодаправильно, и QThread.sleep(5) запускается в главном потоке, вызывая сбой Eventloop и графический интерфейс пользователя.

Таким образом, решение состоит в том, чтобы удалить родительский элемент QTimer, чтобы поток, к которому он принадлежит, былодин из метода run(), и переместите рабочую функцию в тот же метод.С другой стороны, это плохая практика - создавать статические атрибуты без необходимости, учитывая, что приведенный выше код выглядит следующим образом:

import sys
import threading
from PyQt5.QtCore import QThread, QTimer
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget


class WorkerThread(QThread):
    def run(self):
        def work():
            print("working from :" + str(threading.get_ident()))
            QThread.sleep(5)
        print("thread started from :" + str(threading.get_ident()))
        timer = QTimer()
        timer.timeout.connect(work)
        timer.start(10000)
        self.exec_()

class MyGui(QWidget):
    def __init__(self):
        super().__init__()
        self.initUi()
        self.worker = WorkerThread(self)
        print("Starting worker from :" + str(threading.get_ident()))
        self.worker.start()

    def initUi(self):
        self.setGeometry(500, 500, 300, 300)
        self.pb = QPushButton("Button", self)
        self.pb.move(50, 50)


if __name__ == '__main__':    
    app = QApplication(sys.argv)
    gui = MyGui()
    gui.show()
    sys.exit(app.exec_())

Вывод:

Starting worker from :140068367037952
thread started from :140067808999168
working from :140067808999168
working from :140067808999168

Замечания:

  • Тяжелая задача, которая была эмулирована, составляет 5 секунд, и эта задача должна выполняться каждые 10 секунд.Если ваша задача занимает больше времени, чем период, вы должны создать другие потоки.

  • Если ваша задача состоит в выполнении периодической задачи, которая не так трудна, как показ времени, не используйте новые потоки, потому чтоВы добавляете сложность к простой задаче, кроме того, это может сделать этап отладки и тестирования более сложным.

0 голосов
/ 27 августа 2018

Извините, я неправильно понял вопрос.Возможно, вам поможет ответ на другой вопрос.Основное сообщение состоит в том, что вы должны использовать основной цикл событий в Qt, чтобы не останавливать графический интерфейс вместо выполнения потока на __init__: Pyqt5 qthread + сигнал не работает + заморозка графического интерфейса

Вы можете сделать это, используя слоты Qt с декоратором @pyqtSlot().

------------ старый (неправильный) ответ ---------

QTimer уже может работать в отдельных потоках, поэтому я думаю, что вы могли бы сделать это без написания этой части самостоятельно.Например, вы можете сделать то, что вы уже делаете в функции:

 def update_gui(self):
     # edit: here is where you can add your gui update code:
     self.setGeometry(500, 500, 300, 300)
     self.pb = QPushButton("Button", self)
     self.pb.move(50, 50)
     self.show()
     # /edit (this is obviously only the setup code, but you get the idea)

     self.update_timer = QTimer()
     self.update_timer.setInterval(int(5000))
     self.update_timer.timeout.connect(self.update_gui)
     self.update_timer.start()

и вызвать ее в __init__.Вот как я реализовал текстовое поле, которое очищается через пару секунд.

...