PyQt5 and asyncio: yield from never finishes

后端 未结 1 628
轻奢々
轻奢々 2021-02-06 03:09

I\'m trying to create a new application based on PyQt5 and asyncio (with python 3.4, looking forward to eventually upgrade to 3.5 with async/await). My goal is to use asyncio so

1条回答
  •  孤街浪徒
    2021-02-06 03:43

    Ok, that's one plus of SO: Writing down a question makes you think again about everything. Somehow I just figured it out:

    Looking again at the example from the quamash repo, I found that the event loop to use is obtained somewhat differently:

    app = QApplication(sys.argv)
    loop = QEventLoop(app)
    asyncio.set_event_loop(loop)  # NEW must set the event loop
    
    # ...
    
    with loop:
        loop.run_until_complete(master())
    

    The key seems to be the asyncio.set_event_loop(). It is also important to note that the QEventLoop mentioned there is the one from the quamash package, NOT from Qt5. So my example now looks like this:

    import sys
    import quamash
    import asyncio
    from PyQt5.QtWidgets import *
    from PyQt5.QtCore import *
    
    @asyncio.coroutine
    def op():
      print('op()')
    
    
    @asyncio.coroutine
    def slow_operation():
      print('clicked')
      yield from op()
      print('op done')
      yield from asyncio.sleep(0.1)
      print('timeout expired')
      yield from asyncio.sleep(2)
      print('second timeout expired')
    
      loop.stop()
    
    def coroCallHelper(coro):
      asyncio.ensure_future(coro(), loop=loop)
    
    class Example(QWidget):
    
      def __init__(self):
        super().__init__()
        self.initUI()
    
      def initUI(self):
        def btnCallback(obj):
          #~ loop.call_soon(coroCallHelper, slow_operation)
          asyncio.ensure_future(slow_operation(), loop=loop)
          print('btnCallback returns...')
    
        btn = QPushButton('Button', self)
        btn.resize(btn.sizeHint())
        btn.move(50, 50)
        btn.clicked.connect(btnCallback)
    
        self.setGeometry(300, 300, 300, 200)
        self.setWindowTitle('Async')    
        self.show()
    
    app = QApplication(sys.argv)
    loop = quamash.QEventLoop(app)
    asyncio.set_event_loop(loop)  # NEW must set the event loop
    
    with loop:
        w = Example()
        w.show()
        loop.run_forever()
    print('Coroutine has ended')
    

    And it 'just works' now:

    btnCallback returns...
    clicked
    op()
    op done
    timeout expired
    second timeout expired
    Coroutine has ended
    

    Maybe this is of some help for others. I'm happy with it at least ;) Comments on the general pattern are still welcome, of course!

    Addendum: Please note that this works with recent Python versions up to Python 3.7.x if quamash is replaced by asyncqt. However, using the same code with Python 3.8 causes the @coroutine decorators to generate RuntimeWarnings and eventually fails with a RuntimeError: no running event loop in asyncio.sleep(). Maybe someone else knows what to change to get this working again. It might just be that asyncqt is not yet compatible with Python 3.8.

    Regards, Philipp

    0 讨论(0)
提交回复
热议问题