2017-01-11 8 views
2

Основываясь на документах 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), опять же явно не так.

+0

Извините, но я не могу воспроизвести это. Оба метода дают идентичный вывод, и все потоки выполняются за пределами основного потока (это использует python 3.6.0, qt 5.7.1 и pyqt 5.7 в arch-linux). – ekhumoro

+0

@ekhumoro Спасибо, что посмотрели. Geez, это странно, это происходит только тогда, когда я запускаю его через отладчик PyCharm (нет точек останова, кстати). Отладчик Python (pdb) отлично работает. Думаю, я открою билет с JetBrains. – Schollii

ответ

1

Я отправил запрос в службу поддержки на JetBrains и они сразу же ответил:

Это было сделано намеренно для лучшей отладки. Вы можете снять галочку Настройка Настройки | Создание, выполнение, развертывание | Python Debugger> PyQt совместим и будет работать, как и ожидалось.

Удивительно!