Основываясь на документах Qt и других примерах в Интернете, я бы подумал, что следующая программа, которая использует сигнал QThread.started
, начнет работать с не основной нити. Но это не так, то вместо того, чтобы каждый work
слот вызывается из основного нити:Как начать работу в неосновной QObject
import time
import sys
from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QApplication, QPushButton, QTextEdit, QVBoxLayout, QWidget
def trap_exc_during_debug(*args):
# when app exits, put breakpoint in next line when run in debugger, and analyse args
pass
sys.excepthook = trap_exc_during_debug
class Checker(QObject):
sig_step = pyqtSignal(int, str)
sig_done = pyqtSignal(int)
def __init__(self, id: int):
super().__init__()
self.__id = id
@pyqtSlot()
def work(self):
thread_name = QThread.currentThread().objectName()
thread_id = int(QThread.currentThreadId())
print('running work #{} from thread "{}" (#{})'.format(self.__id, thread_name, thread_id))
time.sleep(2)
self.sig_step.emit(self.__id, 'step 1')
time.sleep(2)
self.sig_step.emit(self.__id, 'step 2')
time.sleep(2)
self.sig_done.emit(self.__id)
class MyWidget(QWidget):
NUM_THREADS = 3
sig_start = pyqtSignal()
def __init__(self):
super().__init__()
self.setWindowTitle("Thread Example")
form_layout = QVBoxLayout()
self.setLayout(form_layout)
self.resize(200, 200)
self.push_button = QPushButton()
self.push_button.clicked.connect(self.start_threads)
self.push_button.setText("Start {} threads".format(self.NUM_THREADS))
form_layout.addWidget(self.push_button)
self.log = QTextEdit()
form_layout.addWidget(self.log)
# self.log.setMaximumSize(QSize(200, 80))
self.text_edit = QTextEdit()
form_layout.addWidget(self.text_edit)
# self.text_edit.setMaximumSize(QSize(200, 60))
QThread.currentThread().setObjectName('main')
self.__threads_done = None
self.__threads = None
def start_threads(self):
self.log.append('starting {} threads'.format(self.NUM_THREADS))
self.push_button.setDisabled(True)
self.__threads_done = 0
self.__threads = []
for idx in range(self.NUM_THREADS):
checker = Checker(idx)
thread = QThread()
thread.setObjectName('thread_' + str(idx))
self.__threads.append((thread, checker)) # need to store checker too otherwise will be gc'd
checker.moveToThread(thread)
checker.sig_step.connect(self.on_thread_step)
checker.sig_done.connect(self.on_thread_done)
# self.sig_start.connect(checker.work) # method 1 works: each work() is in non-main thread
thread.started.connect(checker.work) # method 2 doesn't work: each work() is in main thread
thread.start()
self.sig_start.emit() # this is only useful in method 1
@pyqtSlot(int, str)
def on_thread_step(self, thread_id, data):
self.log.append('thread #{}: {}'.format(thread_id, data))
self.text_edit.append('{}: {}'.format(thread_id, data))
@pyqtSlot(int)
def on_thread_done(self, thread_id):
self.log.append('thread #{} done'.format(thread_id))
self.text_edit.append('-- Thread {} DONE'.format(thread_id))
self.__threads_done += 1
if self.__threads_done == self.NUM_THREADS:
self.log.append('No more threads')
self.push_button.setEnabled(True)
if __name__ == "__main__":
app = QApplication([])
form = MyWidget()
form.show()
sys.exit(app.exec_())
Если я использую пользовательский сигнал вместо этого, он отлично работает. Чтобы увидеть это, закомментируйте строку «метод 2» и раскомментируйте строку «метод 1» и повторите прогон.
Было бы лучше начать работу с рабочими, не создавая настраиваемый сигнал, есть ли способ сделать это (при этом придерживаться дизайна вызова moveToThread
на рабочих)?
Примечание: Документы для QThread.started
сигнала не помогут:
Этот сигнал излучается из соответствующей нити, когда она начинает выполнение
Для меня это означает, что started
будет излучаться в не основной теме, так что слот work
, к которому он подключен, будет вызван в не основной поток, но это явно не так. Даже если моя интерпретация неверна и сигнал находится в самом деле, излучаемого в основном потоке, тип соединения для обоих методов Значение по умолчанию Qt.AutoConnection
, в слот на QObject
переехал в другой поток, поэтому started
сигнал должен передаваться асинхронно (т. е. через каждый цикл проверки шага QThread), опять же явно не так.
Извините, но я не могу воспроизвести это. Оба метода дают идентичный вывод, и все потоки выполняются за пределами основного потока (это использует python 3.6.0, qt 5.7.1 и pyqt 5.7 в arch-linux). – ekhumoro
@ekhumoro Спасибо, что посмотрели. Geez, это странно, это происходит только тогда, когда я запускаю его через отладчик PyCharm (нет точек останова, кстати). Отладчик Python (pdb) отлично работает. Думаю, я открою билет с JetBrains. – Schollii