title: QThreadPool - How to interrupt / How to use wisely the waitForDone method
body: <p><strong>Background :</strong></p>
  
  <p>I have a script that allows me to make spatial queries on a PostgreSQL database via an API coming from a private editor (I can't directly query the database). This API is working with python 3.2. To summarize very quickly, this script is used to download elements of this database in the desired geographical footprint. Depending of the zone, you can obtain between 1 to over 100 elements, each of them having very different sizes (from Ko to Go).</p>
  
  <p>The main window let you to set all options and then start the global process. When launched a console window appears letting you see what’s going on. Once an item has been downloaded a short “report” is displayed on the console. Currently everything is done sequentially one element at a time. As you can imagine, if this element is quite large, the console freezes while waiting for the end of the download process.</p>
  
  <p><strong>Code:</strong></p>
  
  <p>I'm not going to post the full script here, but through a very simple script I will try to show the main problem I'm trying to solve (i.e. avoid locking the user interface / have some sort of real-time output on what's going on).</p>
  
  <p>So, in order to avoid these freezing problems, the use of threads seemed to me to be the best solution. To simulate the download process (see previous chapter) I used the url.request urlretrieve method with multiple urls (pointing to files of different sizes).</p>
  
  <pre><code>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_()
  </code></pre>
  
  <p><strong>Questions:</strong></p>
  
  <p>Everything seems to work as expected but I encounter two difficulties:</p>
  
  <ol>
  <li>At the end of the thread pool process I have to make some
  post-processing. If I use the waitForDone method I don't see my
  console until the all work is done and it’s not the type of behavior
  wanted. </li>
  <li>If the Cancel Button in the Console is clicked, I need to interrupt
  the threadpool and I don’t know how to manage that.</li>
  </ol>