Skip to content

Commit 065ac98

Browse files
use asyncio.run() to run the QEventLoop (#138)
Co-authored-by: Alex March <alexmarch@fastmail.com>
1 parent 819a630 commit 065ac98

5 files changed

Lines changed: 86 additions & 24 deletions

File tree

README.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,15 @@ class MainWindow(QWidget):
4646
if __name__ == "__main__":
4747
app = QApplication(sys.argv)
4848

49-
event_loop = QEventLoop(app)
50-
asyncio.set_event_loop(event_loop)
51-
5249
app_close_event = asyncio.Event()
5350
app.aboutToQuit.connect(app_close_event.set)
5451

5552
main_window = MainWindow()
5653
main_window.show()
5754

58-
with event_loop:
59-
event_loop.run_until_complete(app_close_event.wait())
55+
# for 3.11 or older use qasync.run instead of asyncio.run
56+
# qasync.run(app_close_event.wait())
57+
asyncio.run(app_close_event.wait(), loop_factory=QEventLoop)
6058
```
6159

6260
More detailed examples can be found [here](https://github.com/CabbageDevelopment/qasync/tree/master/examples).

examples/aiohttp_fetch.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
QVBoxLayout,
1414
QWidget,
1515
)
16+
1617
from qasync import QEventLoop, asyncClose, asyncSlot
1718

1819

@@ -68,15 +69,16 @@ async def on_btn_fetch_clicked(self):
6869
if __name__ == "__main__":
6970
app = QApplication(sys.argv)
7071

71-
event_loop = QEventLoop(app)
72-
asyncio.set_event_loop(event_loop)
73-
7472
app_close_event = asyncio.Event()
7573
app.aboutToQuit.connect(app_close_event.set)
76-
74+
7775
main_window = MainWindow()
7876
main_window.show()
7977

80-
event_loop.create_task(main_window.boot())
81-
event_loop.run_until_complete(app_close_event.wait())
82-
event_loop.close()
78+
async def async_main():
79+
asyncio.create_task(main_window.boot())
80+
await app_close_event.wait()
81+
82+
# for 3.11 or older use qasync.run instead of asyncio.run
83+
# qasync.run(async_main())
84+
asyncio.run(async_main(), loop_factory=QEventLoop)

examples/executor_example.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import functools
21
import asyncio
3-
import time
2+
import functools
43
import sys
4+
import time
55

66
# from PyQt6.QtWidgets import
77
from PySide6.QtWidgets import QApplication, QProgressBar
8+
89
from qasync import QEventLoop, QThreadExecutor
910

1011

@@ -34,8 +35,6 @@ def last_50(progress, loop):
3435
if __name__ == "__main__":
3536
app = QApplication(sys.argv)
3637

37-
event_loop = QEventLoop(app)
38-
asyncio.set_event_loop(event_loop)
39-
40-
event_loop.run_until_complete(master())
41-
event_loop.close()
38+
# for 3.11 or older use qasync.run instead of asyncio.run
39+
# qasync.run(master())
40+
asyncio.run(master(), loop_factory=QEventLoop)

qasync/__init__.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -331,11 +331,8 @@ class _QEventLoop:
331331
... assert x + y == 4
332332
... await asyncio.sleep(.1)
333333
>>>
334-
>>> loop = QEventLoop(app)
335-
>>> asyncio.set_event_loop(loop)
336-
>>> with loop:
337-
... loop.run_until_complete(xplusy(2, 2))
338-
334+
>>> asyncio.run(xplusy(2, 2), loop_factory=lambda:QEventLoop(app))
335+
339336
If the event loop shall be used with an existing and already running QApplication
340337
it must be specified in the constructor via already_running=True
341338
In this case the user is responsible for loop cleanup with stop() and close()

tests/test_qeventloop.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,72 @@ async def mycoro():
808808
assert "seconds" in msg
809809

810810

811+
@pytest.mark.skipif(sys.version_info < (3, 12), reason="Requires Python 3.12+")
812+
def test_asyncio_run(application):
813+
"""Test that QEventLoop is compatible with asyncio.run()"""
814+
done = False
815+
loop = None
816+
817+
async def main():
818+
nonlocal done, loop
819+
assert loop.is_running()
820+
assert asyncio.get_running_loop() is loop
821+
await asyncio.sleep(0.01)
822+
done = True
823+
824+
def factory():
825+
nonlocal loop
826+
loop = qasync.QEventLoop(application)
827+
return loop
828+
829+
asyncio.run(main(), loop_factory=factory)
830+
assert done
831+
assert loop.is_closed()
832+
assert not loop.is_running()
833+
834+
835+
@pytest.mark.skipif(sys.version_info < (3, 12), reason="Requires Python 3.12+")
836+
def test_asyncio_run_cleanup(application):
837+
"""Test that running tasks are cleaned up"""
838+
task = None
839+
cancelled = False
840+
841+
async def main():
842+
nonlocal task, cancelled
843+
844+
async def long_task():
845+
nonlocal cancelled
846+
try:
847+
await asyncio.sleep(10)
848+
except asyncio.CancelledError:
849+
cancelled = True
850+
851+
task = asyncio.create_task(long_task())
852+
await asyncio.sleep(0.01)
853+
854+
asyncio.run(main(), loop_factory=lambda: qasync.QEventLoop(application))
855+
assert cancelled
856+
857+
858+
def test_qasync_run(application):
859+
"""Test running with qasync.run()"""
860+
done = False
861+
loop = None
862+
863+
async def main():
864+
nonlocal done, loop
865+
loop = asyncio.get_running_loop()
866+
assert loop.is_running()
867+
await asyncio.sleep(0.01)
868+
done = True
869+
870+
# qasync.run uses an EventLoopPolicy to create the loop
871+
qasync.run(main())
872+
assert done
873+
assert loop.is_closed()
874+
assert not loop.is_running()
875+
876+
811877
def teardown_module(module):
812878
"""
813879
Remove handlers from all loggers

0 commit comments

Comments
 (0)