2016-10-11 6 views
2

私はいくつかのマルチスレッドをやっています。私は別のQThreadに送ってworkメソッドを持つワーカークラスを持っています。 workメソッドは内部に条件付きwhileループを持ちます。ワーカーオブジェクトに信号を送信して停止させたい(_runningの条件をfalseに変更したい)。これにより、whileループが終了し、作業者オブジェクト(作業者のスレッドの終了スロットに接続されている)から終了信号が送信されます。PyQt:オブジェクトがwhileループを実行しているスレッドに停止信号を送るには?

信号を介してfalse条件がワーカーオブジェクトに送信されますが、受信されません。これは、whileループがそのスレッドのイベントループをブロックするためと考えられます。たとえ私がQCoreApplication.processEvents()をwhileループの中に入れても、何も起こりません。問題はどこだ?なぜシグナルが処理されないのですか? (ワーカーの停止スロットにあるprint文は決して実行されませんが、奇妙なことは、スレッドが間違った方法で停止しているように見えることです)。ここ

コードされる:

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

これはうまくいきません。作業メソッドから起動されるため、on_worker_doneはクライアント上で呼び出されません。 – U2ros

答えて

4

あなたの例の2つの主要な問題があります。

まず、あなたが労働者を停止する信号を発しているが、信号がクロススレッドであることから、それは受信側のイベント・キューに掲載されますが。ただし、ワーカーはブロッキングwhileループを実行しているので、保留中のイベントは処理できません。これを回避するにはいくつかの方法がありますが、最も単純なのは、信号を使用する代わりに、単にワーカーのstopメソッドを直接呼び出すことです。

第2に、メインスレッドで明示的にイベントループを実行していないため、ワーカーから送信されたクロススレッド信号をキューに入れることはできません。さらに重要なのは、ユーザがキーを押した後にプログラムを終了することも何もないので、クライアントとワーカーはすぐにガベージコレクトされます。以下は

すべての問題を修正し、あなたの例の再書かれたバージョンです:

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

クロススレッド信号/スロット接続は、レシーバ・オブジェクトのスレッドで実行中のイベントループを必要とします。

あなたのケースでは、2番目のスレッドにイベントループがあり、実行中ですが、常に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

関連する問題