Something that I’ve come across whilst delving into the deepest, darkest areas of QNetworking (and as a result, QThreading) are user cases where the main python thread needs to wait for an async object to complete a task before processing.
One of Qt’s biggest advantages, in my opinion, is how everything is built around the concept async, allowing for such easy creation of QNetworking and dealing with QThreadpools etc.
Thanks to these building blocks, it’s easy to create really intricate connected applications that are fast and robust. Due to the multithreading nature of these objects, there are a few user cases where the main thread needs to hold and wait for something in another thread to finish.
As these objects are not exposing the thread that they run on for efficient reasons, we can’t just call join() on them, but there are plenty of tricks!
In the real world, it’s perfectly reasonable to use QTcpSocket for a client networking interface as it handles so much of the heavy lifting, but complications arise if the code is going connect to a server, send some information, and then closeout.
As the connection happens in another thread, we have no ability to hold the main thread until that point.
(Keen-eyed developers might know that QTcpSocket has a method for this reason called waitForConnected, but it’s been documented as unstable on Windows)
A simple way to show this is with this snippet with a FakeNetwork class:
import sys
from PySide import QtCore
class FakeNetwork(QtCore.QObject):
connected = QtCore.Signal()
def __init__(self):
"""
Constructor
"""
super(FakeNetwork, self).__init__()
self.isConnected = False
self._mockConnectionDelay = QtCore.QTimer()
def connectToHost(self, hostName):
"""
Connect to a given host
args:
hostName (str): The host to connect to
"""
self._mockConnectionDelay.start(3000) # 3 Second
self._mockConnectionDelay.timeout.connect(self._handleConnection)
def _handleConnection(self):
"""
Handles a successful connection to a host
"""
self.isConnected = True
self.connected.emit()
app = QtCore.QCoreApplication(sys.argv)
myFakeConnection = FakeNetwork()
myFakeConnection.connectToHost("fake-hostName")
print myFakeConnection.isConnected
sys.exit(app.exec_())
Running it, the .isConnected prints out False, and the application finishes.
Not quite what we want.
By using a QEvenLoop, we can keep checking the and processing events but holding on the main thread in place until the given signal is emitted or the timeout is hit, whichever is first. Implementing it as a method makes it easily re-useable anywhere in the code.
def blockUntilSignal(trigger, timeout=sys.maxint):
"""
Block the main thread until the given signal is emited or the timeout hits
"""
eventLoop = QtCore.QEventLoop()
timer = QtCore.QTimer()
timer.start(timeout)
trigger.connect(eventLoop.quit)
timer.timeout.connect(eventLoop.quit)
eventLoop.exec_()
We can combine the two, as well as a single one liner to call it, giving us the final code:
import sys
from PySide import QtCore
class FakeNetwork(QtCore.QObject):
connected = QtCore.Signal()
def __init__(self):
"""
Constructor
"""
super(FakeNetwork, self).__init__()
self.isConnected = False
self._mockConnectionDelay = QtCore.QTimer()
def connectToHost(self, hostName):
"""
Connect to a given host
args:
hostName (str): The host to connect to
"""
self._mockConnectionDelay.start(3000) # 3 Second
self._mockConnectionDelay.timeout.connect(self._handleConnection)
def _handleConnection(self):
"""
Handles a successful connection to a host
"""
self.isConnected = True
self.connected.emit()
def blockUntilSignal(trigger, timeout=sys.maxint):
"""
Block the main thread until the given signal is emited or the timeout hits
"""
eventLoop = QtCore.QEventLoop()
timer = QtCore.QTimer()
timer.start(timeout)
trigger.connect(eventLoop.quit)
timer.timeout.connect(eventLoop.quit)
eventLoop.exec_()
app = QtCore.QCoreApplication(sys.argv)
myFakeConnection = FakeNetwork()
myFakeConnection.connectToHost("fake-hostName")
# Block until Signal
blockUntilSignal(myFakeConnection.connected)
print myFakeConnection.isConnected
sys.exit(app.exec_())
With the final code in place, the code successful blocks and holds until the connection is established allowing for the .isConnected to print True.
We’ve shown a simple and yet powerful way to block the main code whilst we’re waiting for multithreading objects to run their code.
This design could easily be expanded to take in a list of signals and wait until one of them, such as a ‘connected’ signal or an ‘errored’ signal instead.
I hope that given you less reasons to use unleash’s Qt power on your next application!
– Geoff Samuel
Like!! I blog quite often and I genuinely thank you for your information. The article has truly peaked my interest.