Вопрос-Ответ

Background thread with QThread in PyQt

Фоновый поток с QThread в PyQt

У меня есть программа, которая взаимодействует с радио, которое я использую, через графический интерфейс, который я написал в PyQt. Очевидно, что одной из основных функций радио является передача данных, но чтобы делать это непрерывно, мне приходится выполнять циклическую запись, что приводит к зависанию графического интерфейса. Поскольку я никогда не имел дела с потоковой обработкой, я попытался избавиться от этих зависаний, используя QCoreApplication.processEvents(). Однако радио должно переходить в режим ожидания между передачами, поэтому графический интерфейс по-прежнему зависает в зависимости от того, как долго длятся эти переходы в режим ожидания.

Есть ли простой способ исправить это с помощью QThread? Я искал руководства по реализации многопоточности с помощью PyQt, но большинство из них касаются настройки серверов и гораздо более продвинуты, чем мне нужно. Честно говоря, мне даже не нужно, чтобы мой поток что-либо обновлял во время выполнения, мне просто нужно запустить его, передать в фоновом режиме и остановить.

Переведено автоматически
Ответ 1

Я создал небольшой пример, который показывает 3 разных и простых способа работы с потоками. Я надеюсь, это поможет вам найти правильный подход к вашей проблеме.

import sys
import time

from PyQt5.QtCore import (QCoreApplication, QObject, QRunnable, QThread,
QThreadPool, pyqtSignal)


# Subclassing QThread
# http://qt-project.org/doc/latest/qthread.html
class AThread(QThread):

def run(self):
count = 0
while count < 5:
time.sleep(1)
print("A Increasing")
count += 1

# Subclassing QObject and using moveToThread
# http://blog.qt.digia.com/blog/2007/07/05/qthreads-no-longer-abstract
class SomeObject(QObject):

finished = pyqtSignal()

def long_running(self):
count = 0
while count < 5:
time.sleep(1)
print("B Increasing")
count += 1
self.finished.emit()

# Using a QRunnable
# http://qt-project.org/doc/latest/qthreadpool.html
# Note that a QRunnable isn't a subclass of QObject and therefore does
# not provide signals and slots.
class Runnable(QRunnable):

def run(self):
count = 0
app = QCoreApplication.instance()
while count < 5:
print("C Increasing")
time.sleep(1)
count += 1
app.quit()


def using_q_thread():
app = QCoreApplication([])
thread = AThread()
thread.finished.connect(app.exit)
thread.start()
sys.exit(app.exec_())

def using_move_to_thread():
app = QCoreApplication([])
objThread = QThread()
obj = SomeObject()
obj.moveToThread(objThread)
obj.finished.connect(objThread.quit)
objThread.started.connect(obj.long_running)
objThread.finished.connect(app.exit)
objThread.start()
sys.exit(app.exec_())

def using_q_runnable():
app = QCoreApplication([])
runnable = Runnable()
QThreadPool.globalInstance().start(runnable)
sys.exit(app.exec_())

if __name__ == "__main__":
#using_q_thread()
#using_move_to_thread()
using_q_runnable()
Ответ 2

Примите этот ответ обновленным для PyQt5, python 3.4

Используйте это как шаблон для запуска рабочего процесса, который не принимает данные и возвращает данные в том виде, в каком они доступны форме.

1 - рабочий класс уменьшен и помещен в отдельный файл worker.py для удобства запоминания и самостоятельного использования программного обеспечения.

2 - The main.py файл - это файл, определяющий класс формы GUI

3 - Объект thread не является подклассом.

4 - И объект thread, и рабочий объект принадлежат объекту Form

5 шагов процедуры приведены в комментариях.

# worker.py
from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
import time


class Worker(QObject):
finished = pyqtSignal()
intReady = pyqtSignal(int)


@pyqtSlot()
def procCounter(self): # A slot takes no params
for i in range(1, 100):
time.sleep(1)
self.intReady.emit(i)

self.finished.emit()

И основной файл:

  # main.py
from PyQt5.QtCore import QThread
from PyQt5.QtWidgets import QApplication, QLabel, QWidget, QGridLayout
import sys
import worker


class Form(QWidget):

def __init__(self):
super().__init__()
self.label = QLabel("0")

# 1 - create Worker and Thread inside the Form
self.obj = worker.Worker() # no parent!
self.thread = QThread() # no parent!

# 2 - Connect Worker`s Signals to Form method slots to post data.
self.obj.intReady.connect(self.onIntReady)

# 3 - Move the Worker object to the Thread object
self.obj.moveToThread(self.thread)

# 4 - Connect Worker Signals to the Thread slots
self.obj.finished.connect(self.thread.quit)

# 5 - Connect Thread started signal to Worker operational slot method
self.thread.started.connect(self.obj.procCounter)

# * - Thread finished signal will close the app if you want!
#self.thread.finished.connect(app.exit)

# 6 - Start the thread
self.thread.start()

# 7 - Start the form
self.initUI()


def initUI(self):
grid = QGridLayout()
self.setLayout(grid)
grid.addWidget(self.label,0,0)

self.move(300, 150)
self.setWindowTitle('thread test')
self.show()

def onIntReady(self, i):
self.label.setText("{}".format(i))
#print(i)

app = QApplication(sys.argv)

form = Form()

sys.exit(app.exec_())
Ответ 3

По словам разработчиков Qt, выделение подкласса QThread неверно (см. http://blog.qt.io/blog/2010/06/17/youre-doing-it-wrong / ). Но эту статью действительно трудно понять (плюс название немного снисходительное). Я нашел лучшее сообщение в блоге, в котором дается более подробное объяснение того, почему вы должны использовать один стиль обработки потоков поверх другого: http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation /

Кроме того, я бы настоятельно рекомендовал это видео от KDAB о сигналах и интервалах между потоками.

На мой взгляд, вам, вероятно, никогда не следует создавать подкласс thread с намерением перегрузить метод run . Хотя это и работает, вы в основном обходите то, как Qt хочет, чтобы вы работали. Кроме того, вы упустите такие вещи, как события и надлежащие потокобезопасные сигналы и слоты. Плюс, как вы, вероятно, увидите в приведенном выше сообщении в блоге, "правильный" способ обработки потоков вынуждает вас писать более тестируемый код.

Вот пара примеров того, как воспользоваться преимуществами QThreads в PyQt (я опубликовал отдельный ответ ниже, который правильно использует QRunnable и включает сигналы / слоты, этот ответ лучше, если у вас много асинхронных задач, которые вам нужны для балансировки нагрузки).

import sys
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4.QtCore import Qt

# very testable class (hint: you can use mock.Mock for the signals)
class Worker(QtCore.QObject):
finished = QtCore.pyqtSignal()
dataReady = QtCore.pyqtSignal(list, dict)

@QtCore.pyqtSlot()
def processA(self):
print "Worker.processA()"
self.finished.emit()

@QtCore.pyqtSlot(str, list, list)
def processB(self, foo, bar=None, baz=None):
print "Worker.processB()"
for thing in bar:
# lots of processing...
self.dataReady.emit(['dummy', 'data'], {'dummy': ['data']})
self.finished.emit()


class Thread(QtCore.QThread):
"""Need for PyQt4 <= 4.6 only"""
def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent)

# this class is solely needed for these two methods, there
# appears to be a bug in PyQt 4.6 that requires you to
# explicitly call run and start from the subclass in order
# to get the thread to actually start an event loop

def start(self):
QtCore.QThread.start(self)

def run(self):
QtCore.QThread.run(self)


app = QtGui.QApplication(sys.argv)

thread = Thread() # no parent!
obj = Worker() # no parent!
obj.moveToThread(thread)

# if you want the thread to stop after the worker is done
# you can always call thread.start() again later
obj.finished.connect(thread.quit)

# one way to do it is to start processing as soon as the thread starts
# this is okay in some cases... but makes it harder to send data to
# the worker object from the main gui thread. As you can see I'm calling
# processA() which takes no arguments
thread.started.connect(obj.processA)
thread.start()

# another way to do it, which is a bit fancier, allows you to talk back and
# forth with the object in a thread safe way by communicating through signals
# and slots (now that the thread is running I can start calling methods on
# the worker object)
QtCore.QMetaObject.invokeMethod(obj, 'processB', Qt.QueuedConnection,
QtCore.Q_ARG(str, "Hello World!"),
QtCore.Q_ARG(list, ["args", 0, 1]),
QtCore.Q_ARG(list, []))

# that looks a bit scary, but its a totally ok thing to do in Qt,
# we're simply using the system that Signals and Slots are built on top of,
# the QMetaObject, to make it act like we safely emitted a signal for
# the worker thread to pick up when its event loop resumes (so if its doing
# a bunch of work you can call this method 10 times and it will just queue
# up the calls. Note: PyQt > 4.6 will not allow you to pass in a None
# instead of an empty list, it has stricter type checking

app.exec_()

# Without this you may get weird QThread messages in the shell on exit
app.deleteLater()
Ответ 4

Очень хороший пример от Мэтта, я исправил опечатку, а также pyqt4.8 теперь распространен, поэтому я также удалил фиктивный класс и добавил пример для сигнала dataReady

# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import Qt


# very testable class (hint: you can use mock.Mock for the signals)
class Worker(QtCore.QObject):
finished = QtCore.pyqtSignal()
dataReady = QtCore.pyqtSignal(list, dict)

@QtCore.pyqtSlot()
def processA(self):
print "Worker.processA()"
self.finished.emit()

@QtCore.pyqtSlot(str, list, list)
def processB(self, foo, bar=None, baz=None):
print "Worker.processB()"
for thing in bar:
# lots of processing...
self.dataReady.emit(['dummy', 'data'], {'dummy': ['data']})
self.finished.emit()


def onDataReady(aList, aDict):
print 'onDataReady'
print repr(aList)
print repr(aDict)


app = QtGui.QApplication(sys.argv)

thread = QtCore.QThread() # no parent!
obj = Worker() # no parent!
obj.dataReady.connect(onDataReady)

obj.moveToThread(thread)

# if you want the thread to stop after the worker is done
# you can always call thread.start() again later
obj.finished.connect(thread.quit)

# one way to do it is to start processing as soon as the thread starts
# this is okay in some cases... but makes it harder to send data to
# the worker object from the main gui thread. As you can see I'm calling
# processA() which takes no arguments
thread.started.connect(obj.processA)
thread.finished.connect(app.exit)

thread.start()

# another way to do it, which is a bit fancier, allows you to talk back and
# forth with the object in a thread safe way by communicating through signals
# and slots (now that the thread is running I can start calling methods on
# the worker object)
QtCore.QMetaObject.invokeMethod(obj, 'processB', Qt.QueuedConnection,
QtCore.Q_ARG(str, "Hello World!"),
QtCore.Q_ARG(list, ["args", 0, 1]),
QtCore.Q_ARG(list, []))

# that looks a bit scary, but its a totally ok thing to do in Qt,
# we're simply using the system that Signals and Slots are built on top of,
# the QMetaObject, to make it act like we safely emitted a signal for
# the worker thread to pick up when its event loop resumes (so if its doing
# a bunch of work you can call this method 10 times and it will just queue
# up the calls. Note: PyQt > 4.6 will not allow you to pass in a None
# instead of an empty list, it has stricter type checking

app.exec_()
python multithreading pyqt