2017-11-12 4 views
1

배경 :QThreadPool - 중단하는 방법/어떻게 현명하게 사용하는 waitForDone 방법

나는 내가 (내가 할 수있는 개인 편집기에서 오는 API를 통해 PostgreSQL 데이터베이스에 공간 쿼리를 만들 수있는 스크립트가 데이터베이스에 직접 쿼리하지 마십시오.) 이 API는 파이썬 3.2에서 작동합니다. 요약하면이 스크립트는 원하는 지리학 적 공간에서이 데이터베이스의 요소를 다운로드하는 데 사용됩니다. 영역에 따라 1에서 100 가지 이상의 요소를 얻을 수 있습니다. 각 요소의 크기는 매우 다릅니다 (Ko에서 Go까지).

주 창을 사용하면 모든 옵션을 설정 한 다음 전역 프로세스를 시작할 수 있습니다. 실행되면 콘솔 창이 나타나서 현재 진행중인 작업을 볼 수 있습니다. 항목이 다운로드되면 짧은 "보고서"가 콘솔에 표시됩니다. 현재 모든 것은 한 번에 하나의 요소로 순차적으로 이루어집니다. 상상할 수 있듯이이 요소가 상당히 크면 다운로드 프로세스가 끝날 때까지 기다리는 동안 콘솔이 멈 춥니 다.

코드 :

즉 사용자 잠금 방지 (내가 여기에 전체 스크립트를 게시하지 않을거야,하지만 아주 간단한 스크립트를 통해 내가 해결하기 위해 노력하고있어 큰 문제를 보여주기 위해 노력할 것입니다 인터페이스/무슨 일이 일어나고 있는지에 대한 실시간 출력물을 가지고 있습니다.)

그래서 이러한 결빙 문제를 피하기 위해 스레드를 사용하는 것이 나에게 가장 좋은 솔루션 인 것처럼 보였습니다. 다운로드 프로세스를 시뮬레이션하기 위해 (이전 장 참조) url.request urlretrieve 메소드를 여러 URL (크기가 다른 파일을 가리킴)과 함께 사용했습니다.

import os 
import sys 
import time 
import urllib.request 
from PyQt4 import QtCore, QtGui 

url_1m = 'http://ipv4.sbg.proof.ovh.net/files/1Mio.dat' 
url_10m = 'http://ipv4.sbg.proof.ovh.net/files/10Mio.dat' 
url_100m = 'http://ipv4.sbg.proof.ovh.net/files/100Mio.dat' 
url_1g = 'http://ipv4.sbg.proof.ovh.net/files/1Gio.dat' 
url_10g = 'http://ipv4.sbg.proof.ovh.net/files/10Gio.dat' 

urls = (url_1m, url_10m, url_100m, url_1g, url_10g) 


# --------------------------------------------------------------------------------- 
class DownloadWorkerSignals(QtCore.QObject): 
    """ 
    Defines the signals available from a running download worker thread. 
    """ 
    finished = QtCore.pyqtSignal(str) 


# --------------------------------------------------------------------------------- 
class DownloadWorker(QtCore.QRunnable): 
    """ 
    Worker thread 
    """ 

    def __init__(self, url, filepath, filename, index): 
     super(DownloadWorker, self).__init__() 

     self.url = url 
     self.file_path = filepath 
     self.filename = filename 
     self.index = index 

     self.signals = DownloadWorkerSignals() 

    @QtCore.pyqtSlot(str) 
    def run(self): 
     t = time.time() 
     message = 'Thread %d started\n' % self.index 
     try: 
      # The urlretrieve method will copy a network object to a local file 
      urllib.request.urlretrieve(url=self.url, 
             filename=os.path.join(self.file_path, 
                  self.filename)) 
     except IOError as error: 
      message += str(error) + '\n' 
     finally: 
      message += 'Thread %d ended %.2f s\n' % (self.index, time.time() - t) 
      self.signals.finished.emit(message) # Done 


# --------------------------------------------------------------------------------- 
class Main(QtGui.QMainWindow): 
    """ 
    Main window 
    """ 

    def __init__(self): 
     super(self.__class__, self).__init__() 

     self.resize(400, 200) 
     self.setWindowTitle("Main") 
     self.setWindowModality(QtCore.Qt.ApplicationModal) 

     self.centralwidget = QtGui.QWidget(self) 
     self.setCentralWidget(self.centralwidget) 

     # Ok/Close 
     # ------------------------------------------------------------------------- 
     self.buttonBox = QtGui.QDialogButtonBox(self.centralwidget) 
     self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel | 
              QtGui.QDialogButtonBox.Ok) 
     self.buttonBox.setGeometry(QtCore.QRect(10, 160, 380, 20)) 

     # Connect definition 
     # ------------------------------------------------------------------------- 
     self.connect(self.buttonBox, 
        QtCore.SIGNAL('accepted()'), 
        self.button_ok_clicked) 
     self.connect(self.buttonBox, 
        QtCore.SIGNAL('rejected()'), 
        self.button_cancel_clicked) 

    # Connect functions 
    # ----------------------------------------------------------------------------- 
    def button_cancel_clicked(self): 
     self.close() 

    def button_ok_clicked(self): 
     # Launch console 
     console = Console(parent=self) 
     console.exec_() 


# --------------------------------------------------------------------------------------------------------------- 
class Console(QtGui.QDialog): 
    """ 
    Console window 
    """ 

    def __init__(self, parent): 
     super(self.__class__, self).__init__() 

     self.parent = parent 

     self.resize(400, 200) 
     self.setWindowTitle("Console") 
     self.setModal(True) 

     self.verticalLayout = QtGui.QVBoxLayout(self) 

     # Text edit 
     # ------------------------------------------------------------------------- 
     self.text_edit = QtGui.QPlainTextEdit(self) 
     self.text_edit.setReadOnly(True) 
     self.text_edit_cursor = QtGui.QTextCursor(self.text_edit.document()) 
     self.verticalLayout.addWidget(self.text_edit) 

     # Ok/Close 
     # ------------------------------------------------------------------------- 
     self.button_box = QtGui.QDialogButtonBox(self) 
     self.button_box.setStandardButtons(QtGui.QDialogButtonBox.Close) 
     self.verticalLayout.addWidget(self.button_box) 

     # Connect definition 
     # ------------------------------------------------------------------------- 
     self.connect(self.button_box.button(QtGui.QDialogButtonBox.Close), 
        QtCore.SIGNAL('clicked()'), 
        self.button_cancel_clicked) 

     # Post initialization 
     # ------------------------------------------------------------------------- 
     self.threadpool = QtCore.QThreadPool() 
     self.threadpool.setMaxThreadCount(2) 

     for index, url in enumerate(urls): 
      worker = DownloadWorker(url=url, 
            filepath='C:\\Users\\philippe\\Downloads', 
            filename='url_%d.txt' % index, 
            index=index) 
      worker.signals.finished.connect(self.write_message) 
      self.threadpool.start(worker) 

     ''' 
     I have to wait for the end of the thread pool to make a post-processing. 
     If I use the waitForDone I don't see my console until the all work is done 
     ''' 
     # self.threadpool.waitForDone() 
     # self.write_stram('Thread pool finished') 

    # Connect functions 
    # ----------------------------------------------------------------------------- 
    def button_cancel_clicked(self): 
     if self.threadpool.activeThreadCount() != 0: 
      pass # How to interrupt the threadpool ? 
     self.close() 

    @QtCore.pyqtSlot(str) 
    def write_message(self, text): 
     self.text_edit.insertPlainText(text) 
     cursor = self.text_edit.textCursor() 
     self.text_edit.setTextCursor(cursor) 


# --------------------------------------------------------------------------------- 
if __name__ == '__main__': 
    app = QtGui.QApplication(sys.argv) 
    window = Main() 
    window.show() 
    app.exec_() 

질문 :

모든 것이 예상대로 작동하는 것 같다하지만이 문제가 발생 : 좀 후 처리을해야 스레드 풀 프로세스가 끝나면

  1. 을 . waitForDone 메서드를 사용하면 모든 작업이 완료 될 때까지 내 콘솔이 표시되지 않고 동작 유형이 아닙니다. 싶었습니다.
  2. 콘솔의 취소 버튼을 클릭하면 스레드 풀 을 인터럽트해야하며이를 관리하는 방법을 알지 못합니다.
+0

2 번 질문에 대한 답변을 찾았습니다.

답변

1

나는이 문제에 대해 다른 견해를 가지고있다. (주로 이것에 기반한다 : how-do-i-maintain-a-resposive-gui-using-qthread-with-pyqgis).

그래서 이전의 QThreadPool/QRunnable을 Queue/QThread로 바 꾸었습니다. 아래 코드는 개요를 제공합니다.

import os 
import sys 
import time 
import urllib.request 
import queue 
from PyQt4 import QtCore, QtGui 

url_1m = 'http://ipv4.sbg.proof.ovh.net/files/1Mio.dat' 
url_10m = 'http://ipv4.sbg.proof.ovh.net/files/10Mio.dat' 
url_100m = 'http://ipv4.sbg.proof.ovh.net/files/100Mio.dat' 
url_1g = 'http://ipv4.sbg.proof.ovh.net/files/1Gio.dat' 
url_10g = 'http://ipv4.sbg.proof.ovh.net/files/10Gio.dat' 

urls = (url_1m, url_10m, url_100m, url_1g, url_10g) 


# --------------------------------------------------------------------------------- 
class WorkerThread(QtCore.QThread): 
    """ 
    Worker thread 
    """ 

    def __init__(self, parent_thread): 
     QtCore.QThread.__init__(self, parent_thread) 

    def run(self): 
     self.running = True 
     success = self.do_work() 
     self.emit(QtCore.SIGNAL('jobFinished(PyQt_PyObject)'), success) 

    def stop(self): 
     self.running = False 
     pass 

    def do_work(self): 
     return True 

    def clean_up(self): 
     pass 


# --------------------------------------------------------------------------------- 
class LongRunningTask(WorkerThread): 
    def __init__(self, parent_thread, url, filepath, filename, index): 
     WorkerThread.__init__(self, parent_thread) 

     self.url = url 
     self.filepath = filepath 
     self.filename = filename 
     self.index = index 

    def do_work(self): 
     t = time.time() 
     self.emit(QtCore.SIGNAL('threadText(PyQt_PyObject)'), 'Thread %d started\n' % self.index) 

     try: 
      # The urlretrieve method will copy a network object to a local file 
      urllib.request.urlretrieve(url=self.url, 
             filename=os.path.join(self.filepath, 
                  self.filename)) 
     except IOError as error: 
      self.emit(QtCore.SIGNAL('threadText(PyQt_PyObject)'), 
         'Thread %d error - ' % self.index + str(error) + '\n') 
     finally: 
      self.emit(QtCore.SIGNAL('threadText(PyQt_PyObject)'), 
         'Thread %d ended %.2f s\n' % (self.index, time.time() - t)) 
      return True 


# --------------------------------------------------------------------------------- 
class Console(QtGui.QDialog): 
    """ 
    Console window 
    """ 

    def __init__(self): 
     super(self.__class__, self).__init__() 

     self.resize(400, 200) 
     self.setWindowTitle("Console") 
     self.setModal(True) 

     self.setLayout(QtGui.QVBoxLayout()) 

     # Text edit 
     # ------------------------------------------------------------------------- 
     self.textEdit = QtGui.QPlainTextEdit(self) 
     self.textEdit.setReadOnly(True) 
     self.textEdit_cursor = QtGui.QTextCursor(self.textEdit.document()) 
     self.layout().addWidget(self.textEdit) 

     # Ok/Close 
     # ------------------------------------------------------------------------- 
     self.button_box = QtGui.QDialogButtonBox(self) 
     self.button_box.setStandardButtons(QtGui.QDialogButtonBox.Close) 
     self.button_box.button(QtGui.QDialogButtonBox.Close).setEnabled(False) 
     self.layout().addWidget(self.button_box) 

     # Connect definition 
     # ------------------------------------------------------------------------- 
     self.connect(self.button_box.button(QtGui.QDialogButtonBox.Close), 
        QtCore.SIGNAL('clicked()'), 
        self.reject) 

     # Post-Initialization 
     # ------------------------------------------------------------------------- 
     self.queue = queue.Queue() 
     # self.queue = queue.Queue(maxsize=2) 
     self.run_thread() 

    # Connect functions 
    # ----------------------------------------------------------------------------- 
    def cancel_thread(self): 
     self.workerThread.stop() 

    def job_finished_from_thread(self, success): 
     self.workerThread.stop() 
     self.queue.get() 

     # Stop the pulsation 
     if self.queue.empty(): 
      self.button_box.button(QtGui.QDialogButtonBox.Close).setEnabled(True) 

     self.emit(QtCore.SIGNAL('jobFinished(PyQt_PyObject)'), success) 

    def text_from_thread(self, value): 
     self.textEdit.insertPlainText(value) 
     cursor = self.textEdit.textCursor() 
     self.textEdit.setTextCursor(cursor) 

    def run_thread(self): 
     for index, url in enumerate(urls): 
      self.workerThread = LongRunningTask(parent_thread=self, 
               url=url, 
               filepath='C:\\Users\\philippe\\Downloads', 
               filename='url_%d.txt' % index, 
               index=index) 
      self.connect(self.workerThread, 
         QtCore.SIGNAL('jobFinished(PyQt_PyObject)'), 
         self.job_finished_from_thread) 
      self.connect(self.workerThread, 
         QtCore.SIGNAL('threadText(PyQt_PyObject)'), 
         self.text_from_thread) 

      self.queue.put(self.workerThread) 
      self.workerThread.start() 

      # If I set the queue to maxsize=2, how to manage it here 
      ''' 
      while not self.queue.full(): 
       self.queue.put(self.workerThread) 
       self.workerThread.start() 
      ''' 

# --------------------------------------------------------------------------------- 
if __name__ == '__main__': 
    app = QtGui.QApplication(sys.argv) 
    window = Console() 
    window.show() 
    app.exec_() 

는 질문 : 불행하게도, 나는 어려움의 다른 유형을 발생합니다. 실제로 대기열에는 많은 양의 스레드 (100 개 이상)가 포함될 수 있습니다. 1. QthreadPool과 setMaxThreadCount 메소드와 같이 시스템이 완전히 접히지 않도록 병렬로 실행되는 스레드 수를 어떻게 관리 할 수 ​​있습니까?