2016-10-11 4 views
2

Я делаю несколько многопотоков. У меня есть рабочий класс с методом work, который я отправляю в отдельный QThread. Метод work имеет условный цикл внутри. Я хочу, чтобы можно было отправить сигнал рабочему объекту, чтобы остановить его (изменение условия _running на false). Это вызовет выход цикла while и готовый сигнал, который будет отправлен из рабочего объекта (который подключен к слоту выхода из потока рабочего).PyQt: Как отправить сигнал остановки в поток, в котором объект работает в замкнутом цикле?

Ложное условие отправляется объекту-работнику по сигналу, но оно никогда не принимается, что я считаю потому, что цикл while блокирует цикл событий его потока. Даже если я поставлю QCoreApplication.processEvents() внутри цикла while, ничего не произойдет. В чем проблема? Почему сигнал не обрабатывается? (Обратите внимание, что оператор печати в слоте остановки на рабочем месте никогда не выполняется, но, как ни странно, поток, похоже, не работает неправильно).

Вот код:

import time, sys 
from PyQt4.QtCore import * 
from PyQt4.QtGui import * 

class Worker(QObject): 
    sgnFinished = pyqtSignal() 

    def __init__(self, parent): 
     QObject.__init__(self, parent) 

     self._running = True 

    @pyqtSlot() 
    def stop(): 
     print 'stop signal received, switching while loop condition to false' 
     self._running = False 

    @pyqtSlot()  
    def work(self): 
     while self._running:     #this blocks the thread, if changed to an if clause, thread finishes as expected! 
      QCoreApplication.processEvents() #this doesn't help! 
      time.sleep(0.1) 
      print 'doing work...' 

     #do some cleanup here, then signal the worker is done 
     self.sgnFinished.emit() 


class Client(QObject): 
    sgnStop = pyqtSignal() 

    def __init__(self, parent): 
     QObject.__init__(self, parent) 

     self._thread = None 
     self._worker = None 

    def toggle(self, enable): 
     if enable: 
      if not self._thread: 
       self._thread = QThread() 

      self._worker = Worker(None) 
      self._worker.moveToThread(self._thread) 

      self._worker.sgnFinished.connect(self.on_worker_done) 
      self.sgnStop.connect(self._worker.stop) 

      self._thread.started.connect(self._worker.work) 
      self._thread.start() 
     else: 
      print 'sending stop signal to the worker object' 
      self.sgnStop.emit() #send a queuedconnection type signal to the worker, because its in another thread 

    @pyqtSlot() 
    def on_worker_done(self): 
     print 'workers job was interrupted manually' 
     self._thread.quit() 
     #self._thread.wait() not sure this is neccessary 

if __name__ == '__main__': 
    app = QCoreApplication(sys.argv) 

    client = Client(None) 
    client.toggle(True) 
    raw_input('Press something') 
    client.toggle(False) 
+0

http://stackoverflow.com/questions/32952474/non-blocking-worker-interrupt-file-copy – dtech

+0

Я нашел решение, которое я не понимаю. Замена объявления соединения с сигналом останова с этим (добавление обработчика как выражения лямбда) устраняет проблему: self.sgnStop.connect (lambda: self._worker.stop()) – U2ros

+0

Ну, не совсем работает, кажется, sgnFinished никогда уволен из метода работы, поэтому on_worker_done никогда не вызывается на клиенте. – U2ros

ответ

4

Есть две основные проблемы в вашем примере:

Во-первых, вы излучающие сигнал, чтобы остановить рабочий, но так как сигнал является кросс-нить, она будет размещена в событии очереди приемника. Тем не менее, рабочий выполняет блокировку while-loop, поэтому незавершенные события не могут быть обработаны. Есть несколько способов обойти это, но, вероятно, самым простым является просто вызвать метод рабочего stop непосредственно вместо использования сигнала.

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

Ниже переписана версия вашего примера, который фиксирует все вопросы:

import time, sys 
from PyQt4.QtCore import * 
from PyQt4.QtGui import * 

class Worker(QObject): 
    sgnFinished = pyqtSignal() 

    def __init__(self, parent): 
     QObject.__init__(self, parent) 
     self._mutex = QMutex() 
     self._running = True 

    @pyqtSlot() 
    def stop(self): 
     print 'switching while loop condition to false' 
     self._mutex.lock() 
     self._running = False 
     self._mutex.unlock() 

    @pyqtSlot() 
    def work(self): 
     while self._running: 
      time.sleep(0.1) 
      print 'doing work...' 
     self.sgnFinished.emit() 

class Client(QObject): 
    def __init__(self, parent): 
     QObject.__init__(self, parent) 
     self._thread = None 
     self._worker = None 

    def toggle(self, enable): 
     if enable: 
      if not self._thread: 
       self._thread = QThread() 

      self._worker = Worker(None) 
      self._worker.moveToThread(self._thread) 
      self._worker.sgnFinished.connect(self.on_worker_done) 

      self._thread.started.connect(self._worker.work) 
      self._thread.start() 
     else: 
      print 'stopping the worker object' 
      self._worker.stop() 

    @pyqtSlot() 
    def on_worker_done(self): 
     print 'workers job was interrupted manually' 
     self._thread.quit() 
     self._thread.wait() 
     if raw_input('\nquit application [Yn]? ') != 'n': 
      qApp.quit() 

if __name__ == '__main__': 

    # prevent some harmless Qt warnings 
    pyqtRemoveInputHook() 

    app = QCoreApplication(sys.argv) 

    client = Client(None) 

    def start(): 
     client.toggle(True) 
     raw_input('Press something\n') 
     client.toggle(False) 

    QTimer.singleShot(10, start) 

    sys.exit(app.exec_()) 
1

перемычки сигнал/слот резьба требуют запущенного цикла обработки событий в потоке объекта приемника.

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

Таким образом, все события вызова слота застревают в очереди событий цикла событий.

Если вы хотите взломать это, как вы попытались с помощью QCoreApplication.processEvents, вы можете попробовать получить сообщение eventDispatcher и называть его processEvent.

Если вам нужно только закончить работника, вы можете назвать его requestInteruption, и вместо проверки на self._running вы проверяете размер потока isInterruptionRequested.

+0

OP использует PyQt4, поэтому 'requestInteruption' недоступен (т. Е. Qt> = 5.2). – ekhumoro

+0

Да, requestInteruption не доступен в Qt 4.8 – U2ros

+0

@ U2ros. Первое предложение этого ответа наиболее актуально для вашей проблемы. Ваш пример просто не будет работать должным образом, если в главном потоке не будет запущен цикл событий. – ekhumoro