Håkon Hægland
Håkon Hægland

Reputation: 40778

How to use callbacks and futures with asyncio and the PyQt5 event loop?

This is a small test program I wrote trying to understand how I can use the PyQt eventloop with asyncio:

import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton
from PyQt5 import QtCore
import qasync
import asyncio

async def main():
    app = QApplication(sys.argv)
    asyncio.set_event_loop(qasync.QEventLoop(app))
    window = QMainWindow()
    window.setGeometry(100, 100, 200, 200)
    button = QPushButton(window)
    button.move(50, 50)
    button.resize(100, 100)
    button.setText('Run')
    future = None
    def run_action():
        future.set_result('run clicked')
    button.clicked.connect(run_action)
    window.setWindowTitle('Testing async event loop')
    window.show()
    await asyncio.sleep(1)
    i = 0
    while True:
        future = asyncio.get_event_loop().create_future()
        result = await future
        i += 1
        if i == 4:
            break
    print("Done")

asyncio.run(main())

This gives me the following error:

Traceback (most recent call last):
  File "/home/hakon/test/python/async/./t.py", line 36, in <module>
    asyncio.run(main())
  File "/home/hakon/.pyenv/versions/3.9.4/lib/python3.9/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/home/hakon/.pyenv/versions/3.9.4/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
    return future.result()
  File "/home/hakon/test/python/async/./t.py", line 25, in main
    await asyncio.sleep(1)
  File "/home/hakon/.pyenv/versions/3.9.4/lib/python3.9/asyncio/tasks.py", line 654, in sleep
    return await future
RuntimeError: Task <Task pending name='Task-1' coro=<main() running at /home/hakon/test/python/async/./t.py:25> cb=[_run_until_complete_cb() at /home/hakon/.pyenv/versions/3.9.4/lib/python3.9/asyncio/base_events.py:184]> got Future <Future pending> attached to a different loop

Any idea what I am missing here? How can I improve this code?

Upvotes: 3

Views: 1392

Answers (1)

eyllanesc
eyllanesc

Reputation: 244132

The problem is that you are running an eventloop and then you have just created the Qt eventloop. There are 2 possible solutions:

  • Set the eventloop before executing the coroutine main:

    import asyncio
    import sys
    
    from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton
    import qasync
    
    
    async def main():
        window = QMainWindow()
        window.setGeometry(100, 100, 200, 200)
        button = QPushButton(window)
        button.move(50, 50)
        button.resize(100, 100)
        button.setText("Run")
        future = None
    
        def run_action():
            if future is not None:
                future.set_result("run clicked")
    
        button.clicked.connect(run_action)
        window.setWindowTitle("Testing async event loop")
        window.show()
        await asyncio.sleep(1)
        i = 0
        while True:
            future = asyncio.get_event_loop().create_future()
            result = await future
            i += 1
            if i == 4:
                break
        print("Done")
    
    
    app = QApplication(sys.argv)
    loop = qasync.QEventLoop(app)
    asyncio.set_event_loop(loop)
    with loop:
        loop.run_until_complete(main())
    
  • Or use qasync.run():

    import asyncio
    import sys
    
    from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton
    import qasync
    
    
    async def main():
        window = QMainWindow()
        window.setGeometry(100, 100, 200, 200)
        button = QPushButton(window)
        button.move(50, 50)
        button.resize(100, 100)
        button.setText("Run")
        future = None
    
        def run_action():
            if future is not None:
                future.set_result("run clicked")
    
        button.clicked.connect(run_action)
        window.setWindowTitle("Testing async event loop")
        window.show()
        await asyncio.sleep(1)
        i = 0
        while True:
            future = asyncio.get_event_loop().create_future()
            result = await future
            i += 1
            if i == 4:
                break
        print("Done")
    
    
    qasync.run(main())
    

Upvotes: 3

Related Questions