-
-
Save aviogreen/75c0674e5fe238f4d765fb818ea7a5d2 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 hidden or 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