PySide / PyQt coroutines

298 views
Skip to first unread message

erik

unread,
May 12, 2016, 11:31:23 PM5/12/16
to Tornado Web Server
What is the best way to use Tornado coroutines with a GUI such as PySide or PyQt? Because the GUI has its own event loop, it's not so easy to run the Tornado loop. It's also not as easy as just running Tornado in a background thread because all GUI code has to be run on the main thread. If not, it will glitch or crash.
I have gotten things working, but it's not completely ideal.
I basically subclassed SelectIOLoop so that I can split its start method into two methods - start and update:

class EventLoop(SelectIOLoop):
   
def start(self):
       
# The outside of the while loop in SelectIOLoop.start
       
try:
           
while True:
               
if self.update():
                   
break
       
# The outside of the while loop in SelectIOLoop.start
   
   
def update(self):
       
# The inside of the while loop in SelectIOLoop.startclass



Then I run update at a frequent interval using a QTimer class:

class QtEventLoop(QObject):
    _instance
= None

   
@staticmethod
   
def instance():
       
if not QtEventLoop._instance:
           
QtEventLoop._instance = QtEventLoop()

       
return QtEventLoop._instance

   
def __init__(self):
       
super(QtEventLoop, self).__init__()
       
       
self._eventLoop = EventLoop()
       
self._eventLoop.install()
       
self._eventLoop._setup_logging()
       
       
self._updateTimer = QTimer()
       
# Default is 60 updates / second
        defaultInterval
= int((1.0 / 60.0) * 1000.0) + 1
       
self._updateTimer.setInterval(defaultInterval)
       
self._updateTimer.timeout.connect(self._onTimeout)
   
   
def start(self):
       
if not self._updateTimer.isActive():
           
self._updateTimer.start()
   
   
@Slot()
   
def _onTimeout(self):
       
self._eventLoop.update()


Then in my main window, I start the loop like this:

qtEventLoop = QtEventLoop.instance()
qtEventLoop
.start()



The reason I say this is not ideal is because it doesn't work like a plugin. Instead, I have to copy the guts of the core Tornado source and copy them into my EventLoop class. So I'm hoping there is a better way. 
Thanks.

Ben Darnell

unread,
May 13, 2016, 1:25:32 PM5/13/16
to Tornado Mailing List
There are three ways to do this:

The best way is to implement the IOLoop interface in terms of the QT event loop. I don't know of anyone who has done this for Tornado+QT, but you should be able to get this indirectly through asyncio or twisted:

    asyncio.set_event_loop(quamash.QEventLoop())
    tornado.platform.asyncio.AsyncIOMainLoop().install()

or

    qtreactor.pyqt4reactor.install()
    tornado.platform.twisted.TwistedIOLoop().install()

The second way is to run each event loop in its own thread. You'll need to be careful about synchronization, and use methods like IOLoop.add_callback() to transfer control from QT to Tornado, and QT's analogous method to transfer back to QT. 

Finally, you can run one event loop inside the other for brief intervals (as you've done here). This is not ideal because it means that the subordinate event loop is less responsive to events (or it wastes CPU with frequent polling), so I don't generally recommend it. However, if you want to take this path you can do so without copying IOLoop's internals (or hard-coding the choice of SelectIOLoop):

    @Slot()
    def _onTimeout(self):
        self._ioLoop.add_callback(self._ioLoop.stop)
        self._ioLoop.start()

-Ben


--
You received this message because you are subscribed to the Google Groups "Tornado Web Server" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python-tornad...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

erik

unread,
May 13, 2016, 6:22:42 PM5/13/16
to Tornado Web Server, b...@bendarnell.com
Thank you for your reply. I'll give these a try.
Reply all
Reply to author
Forward
0 new messages