3

I would like to know how to pause a QThread and then resume when I get a signal. I have read and know that I can do something like this:

def run(self):
    ...
    self.ready=False
    while not self.ready:
        self.sleep(1)
    ...
...
@QtCore.Slot()
def set_ready(self):
    self.ready = True

However, what I want to do is avoid the polling in the thread. I don't want to have to set the sleep to a short amount of time and keep checking. I want to go to sleep until I get a signal from my main thread to continue.

What I am doing in my thread is this:

(pseudo code)
with open file:
    read a block of data
    while data:
        sendThread = send the block via UDP
        read the next block of data
        while not ready or sendThread.isRunning:
            sleep(0.1)

In my main thread I have setup a QtNetwork.QUdpSocket to connect readyRead to a method to handle incoming datagrams and decode them. When it gets the response that I'm waiting for it sends a signal to the set_ready slot to tell the thread to send another datagram. I don't always know how long it will take for the other system to respond, though I will likely have some long timeout value of 30seconds or so.

Is there a way to interrupt the sleep of the thread? so I could do something like this:

sleep(30)
if not ready:
    Timeout occurred stop processing
else:
    Continue processing.
Tom Myddeltyn
  • 1,307
  • 1
  • 13
  • 27

1 Answers1

5

You can do this pretty easily using the worker pattern of using QThreads. There's an example in the QThread documentation. The exact code will be a little different depending on whether you're using PyQt or PySide (it looks like you're using PySide from your example).

One notable issue with PySide compared to PyQt is that they didn't wrap QtCore.Q_ARG, so in PyQt, where you could normally use QMetaObject.invokeMethod to call a slot (with arguments) on the Worker object from the main thread, you can't do that directly in PySide and have to create a dummy signal (ie. Main.send_signal) to connect to the slot on the worker so you can call it from the main thread.

import time
import sys
from PySide import QtCore, QtGui


class Worker(QtCore.QObject):

    send_signal = QtCore.Signal(str) # using PySide
    # QtCore.pyqtSignal(str) ## using PyQt

    # @QtCore.pyqtSlot(str)
    @QtCore.Slot(str)
    def receive_slot(self, data):
        # data could be a filepath
        # open file
        # ... do stuff
        # close file
        QtCore.QThread.sleep(1) # to simulate doing stuff
        self.send_signal.emit(data + ' success')


class Main(QtGui.QWidget):

    send_signal = QtCore.Signal(str)

    def __init__(self):
        super(Main, self).__init__()
        self.worker = Worker()
        self.thread = QtCore.QThread(self)
        self.worker.moveToThread(self.thread)
        self.worker.send_signal.connect(self.receive_slot)
        self.send_signal.connect(self.worker.receive_slot)
        self.thread.start()
        self.send_signal.emit('Start')

    @QtCore.Slot(str)
    def receive_slot(self, data):
        print 'Main: {}'.format(data)
        self.send_signal.emit('Message {}'.format(time.time()))


if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    window = Main()
    window.show()
    app.exec_()
Community
  • 1
  • 1
Brendan Abel
  • 35,343
  • 14
  • 88
  • 118
  • I'm pretty new to threaded programming, so bear with me. I moved my file processing to a separate thread is that it was hanging up the GUI and I want to allow the user to "Cancel" the process. How do I open the file with your example? I typically do the `with open(filename, 'rb') as binaryFile:` methodology. Is that even possible with this or do I need to open the file when the `QThread` runs then close it on termination? I want to make sure that the file gets closed properly. I think I would open the file on `__init__` and do processing in the `slot` and then close when the thread ends? – Tom Myddeltyn May 09 '16 at 20:44
  • Do you really want to keep the file open that whole time? Can you just read the whole file into memory and then close it? It is possible to open the file, do some operations and then close it later, but you can't pause operation in the middle of a `with` context without also pausing the main thread event loop (which will freeze the GUI). Can you just move **the entire operation, including file opening** into the worker thread? That way there is no reason to keep having to send data back and forth between the main and worker threads. – Brendan Abel May 09 '16 at 21:00
  • The issue is that the file is on the large side, I guess I could load the whole thing into memory. One of the issues I am trying to go after is that the program I am replacing (Written in Java) is taking a long time to load the file before the user can process it. So I was attempting to avoid that by loading chunks at a time to process. I should see how much of a time hit I get by loading the file entirely. – Tom Myddeltyn May 09 '16 at 21:06
  • 1
    @busfault Updated the answer to show what I mean. If you want the user to be able to cancel, you can create an instance variable on the worker called `cancel` that is set to `False`, and check it periodically in your worker function. If the main thread wants to cancel the worker thread, just have it set that variable `self.worker.cancel = True`. When the worker checks the variable and it's `True` it should stop. – Brendan Abel May 09 '16 at 21:08
  • @busfault If you open the file in the thread, you can continue reading in chunks, and just send data back to the main thread as it becomes available. – Brendan Abel May 09 '16 at 21:09
  • Brendan, thanks, however I do have the cancel part working in my inherited thread class. – Tom Myddeltyn May 09 '16 at 21:09
  • I am opening the file in the thread already, the main thread doesn't need the data from the file at all. I just need to read chunks and send a datagram, then I need to wait for a specific datagram to indicate that the job is done and I can send another datagram. There are other datagrams coming in that do not pertain to the operation too that need to be handled. – Tom Myddeltyn May 09 '16 at 21:11
  • 1
    You can continue to use the inherited `Thread` method as opposed to the *worker model*, but just realize that the `QThread` actually lives in the main thread, only the `run` function actually executes in a separate thread, so any signals or slots you create on the thread actually run in the main thread. If you're sending data back and forth between the main and worker thread, it's usually better to use the worker model. – Brendan Abel May 09 '16 at 21:12
  • Thank you! I wasn't quite sure what that meant, but now it is starting to make some more sense. I will attempt to re-implement with that model. – Tom Myddeltyn May 09 '16 at 21:14