Created
October 1, 2015 10:57
-
-
Save maartenbreddels/9a31e4025ef9cc0ee68a to your computer and use it in GitHub Desktop.
Combining Qt and tornado, both which want to have their own event loop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
__author__ = 'breddels' | |
""" | |
Demonstrates combining Qt and tornado, both which want to have their own event loop. | |
The solution is to run tornado in a thread, the issue is that callbacks will then also be executed in this thread, and Qt doesn't like that. | |
To fix this, I show how to use execute the callback in the main thread, using a Qt signal/event in combination with Promises. | |
The output of the program is: | |
fetch page, we are in thread <_MainThread(MainThread, started 47200787479520)> | |
response is 191548 bytes, we are in thread <Thread(Thread-1, started daemon 47201018689280)> | |
the other thread should fulfil the result to this promise, we are in thread <Thread(Thread-1, started daemon 47201018689280)> | |
we received a promise, let us fulfill it, and are in thread <_MainThread(MainThread, started 47200787479520)> | |
let us set the background to black, we are in thread <_MainThread(MainThread, started 47200787479520)> | |
The magic happens in this line: | |
.then(self.move_to_gui_thread) | |
Without it, you'll see something like this: | |
fetch page, we are in thread <_MainThread(MainThread, started 47822588292064)> | |
response is 191604 bytes, we are in thread <Thread(Thread-1, started daemon 47822819497728)> | |
let us set the background to black, we are in thread <Thread(Thread-1, started daemon 47822819497728)> | |
QPixmap: It is not safe to use pixmaps outside the GUI thread | |
""" | |
from aplus import Promise # https://github.com/xogeny/aplus | |
import threading | |
import tornado | |
from tornado.httpclient import AsyncHTTPClient | |
from PyQt4 import QtGui | |
from PyQt4 import QtCore | |
import sys | |
# tornado works with futures, this wraps it in a promise | |
def wrap_future_with_promise(future): | |
promise = Promise() | |
def callback(future): | |
e = future.exception() | |
if e: | |
promise.reject(e) | |
else: | |
promise.fulfill(future.result()) | |
future.add_done_callback(callback) | |
return promise | |
class Window(QtGui.QMainWindow): | |
signal_promise = QtCore.pyqtSignal(object, object) | |
def __init__(self): | |
QtGui.QMainWindow.__init__(self) | |
self.button = QtGui.QPushButton("Async fetch using tornado", self) | |
self.button.resize(self.button.sizeHint()) | |
self.button.clicked.connect(self.on_click) | |
self.signal_promise.connect(self.on_signal_promise) | |
def on_click(self, *args): | |
print "fetch page, we are in thread", threading.currentThread() | |
client = AsyncHTTPClient() | |
future = client.fetch("http://www.google.com/") | |
promise = wrap_future_with_promise(future) | |
# without .then(self.move_to_gui_thread), Qt will complain | |
promise.then(self.show_output)\ | |
.then(self.move_to_gui_thread)\ | |
.then(self.do_gui_stuff)\ | |
.then(None, self.on_error) | |
def move_to_gui_thread(self, value): | |
promise = Promise() | |
print "the other thread should fulfil the result to this promise, we are in thread", threading.currentThread() | |
self.signal_promise.emit(promise, value) | |
return promise | |
def on_signal_promise(self, promise, value): | |
print "we received a promise, let us fulfill it, and are in thread", threading.currentThread() | |
promise.fulfill(value) | |
def on_error(self, error): | |
print "error", error | |
def show_output(self, response): | |
print "response is", len(response.body), "bytes, we are in thread", threading.currentThread() | |
def do_gui_stuff(self, response): | |
print "let us set the background to orange, we are in thread", threading.currentThread() | |
# this Qt call should only be done from the main thread | |
self.setStyleSheet("background-color: orange;") | |
# run the tornado loop in a seperate thread | |
thread = threading.Thread(target=lambda : tornado.ioloop.IOLoop.current().start()) | |
thread.setDaemon(True) | |
thread.start() | |
app = QtGui.QApplication(sys.argv) | |
window = Window() | |
window.show() | |
app.exec_() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Note that this solution:
https://gist.github.com/mivade/35cbd270f49e1ca7ba7d
doesn't demonstrate the issue, since setText doesn't trigger it, adding:
self.setStyleSheet("background-color: orange;")
to the update_label method does trigger the
"QPixmap: It is not safe to use pixmaps outside the GUI thread"
warning