CabbageDevelopment/qasync

"RuntimeError: no running event loop" from asyncio.create_task

SJChannel opened this issue · 3 comments

I am testing with python 3.12.3 and PySide6 6.7.0. When using qasync >= 0.24.2, the attached script fails on the call to asyncio.create_task with this exception:

Traceback (most recent call last):
  File "/Users/jdp/python/./qtest.py", line 38, in <module>
    aw = MyAppWindow()
         ^^^^^^^^^^^^^
  File "/Users/jdp/python/./qtest.py", line 22, in __init__
    self.task = asyncio.create_task(self.my_task())
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/asyncio/tasks.py", line 417, in create_task
    loop = events.get_running_loop()
           ^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: no running event loop
sys:1: RuntimeWarning: coroutine 'MyAppWindow.my_task' was never awaited

The failure does not occur with qasync 0.24.0. Uncommenting the statement

#asyncio.events._set_running_loop(loop)

works around the problem, but it is undesirable because it uses a private API.
qtest.py.txt

A different solution that seems to work OK is to create the event loop like this:

loop = qasync.QEventLoop(qApp, already_running=True)

Is that how it should be done? The docstring for class _QEventLoop says, "If the event loop shall be used with an existing and already running QApplication it must be specified in the constructor via already_running=True". I am a little bit confused by that. In my example, the QApplication does indeed exist, but it is not yet running when the QEventLoop is created. (I.e., QApplication.exec() has not been called yet.)

When you are within MainWindow class, my work around is to run asyncio.create_task in QTimer

class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
 
        # function provided from .ui file generated to .py
        self.setupUi(self)

        ... rest of your code / configuration 

        # Trigger after event loop is set for asyncio
        QtCore.QTimer.singleShot(10, self.setup_after_asyncio_event_loop)
    
    def setup_after_asyncio_event_loop(self):
        asyncio.create_task(...)
        asyncio.create_task(...)

With this you most probably running asyncio.create_task after event loop is set, and you wont need to make any changes to event loop. I tried QTimer with 1ms do works, but use 10ms to make sure that the program has more time to setup the event loop before it get used in the rest of the program.

Hope this helps.

I'm also having the same issue. Is this a bug or just something we will always have to work around? I used this workaround.

When you are within MainWindow class, my work around is to run asyncio.create_task in QTimer

My program:

import sys
import asyncio
from qasync import QApplication, QEventLoop, asyncSlot
from PySide6.QtWidgets import QMainWindow, QLabel
from PySide6.QtCore import QTimer

async def hello():
	print('hello')

class MainWindow(QMainWindow):
	def __init__(self) -> None:
		super().__init__()
		self.setWindowTitle('Client App')

		self.label = QLabel('Main Window')
		self.setCentralWidget(self.label)
		QTimer.singleShot(100, self.ready)

	@asyncSlot()
	async def ready(self):
		task = asyncio.create_task(hello())

	def showEvent(self, event):
		print('main window show event')
	
if __name__ == "__main__":
	print('app starting')

	app = QApplication(sys.argv)

	event_loop = QEventLoop(app)
	asyncio.set_event_loop(event_loop)

	app_close_event = asyncio.Event()
	app.aboutToQuit.connect(app_close_event.set)

	main_window = MainWindow()
	main_window.show()

	with event_loop:
		event_loop.run_until_complete(app_close_event.wait())