基于qasync与PySide6的asyncio程序:何时可调度事件循环任务?
问题描述
使用如下代码启动PySide6 + qasync程序时,在MainWindow的__init__或showEvent中调度的asyncio任务,要等到主窗口关闭后才会执行;但通过按钮触发调度时任务可正常运行,希望实现任务自动触发。
启动代码:
async def main(): logger.info('client app starting') app = QApplication(sys.argv) main = MainWindow() main.show() loop = QEventLoop(app) with loop: loop.run_forever() if __name__ == '__main__': initLogging(fileName=os.path.join('log','client.log')) asyncio.run(main())
环境版本:
python 3.12 PySide 6.7.3 qasync 0.27.1
可复现问题的示例代码(hello函数输出会在窗口关闭后打印):
import sys import asyncio from qasync import QApplication, QEventLoop from PySide6.QtWidgets import QMainWindow, QLabel async def hello(): print('hello') class MainWindow(QMainWindow): def __init__(self) -> None: super().__init__() self.setWindowTitle('Client App') task = asyncio.create_task(hello()) self.label = QLabel('Main Window') self.setCentralWidget(self.label) def showEvent(self, event): print('main window show event') task = asyncio.create_task(hello()) def focusInEvent(self, event): print('main window focus in event') async def main(): print('app starting') app = QApplication(sys.argv) main = MainWindow() main.show() loop = QEventLoop(app) with loop: loop.run_forever() if __name__ == '__main__': asyncio.run(main())
问题原因
在main()函数中,main.show()触发showEvent时,qasync的QEventLoop还未启动,此时当前活跃的asyncio事件循环是asyncio.run()创建的临时循环。后续启动的QEventLoop会接管事件循环,但之前创建的任务绑定在旧循环中,旧循环被挂起直到程序结束才会执行这些任务,导致任务延迟到窗口关闭后才运行。
解决方案
方法1:在QEventLoop启动后提交任务
在main()函数的with loop:块内、loop.run_forever()之前创建任务,此时QEventLoop已成为当前活跃的asyncio循环,任务会立即执行:
async def main(): print('app starting') app = QApplication(sys.argv) main = MainWindow() main.show() loop = QEventLoop(app) with loop: # 此时QEventLoop已接管当前asyncio循环 asyncio.create_task(hello()) loop.run_forever()
方法2:通过Qt定时器延迟提交任务(适合窗口事件触发)
如果需要在窗口showEvent中触发任务,使用QTimer.singleShot(0, ...)将任务提交到Qt事件队列,确保QEventLoop启动后再执行任务创建逻辑:
import sys import asyncio from qasync import QApplication, QEventLoop 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) def showEvent(self, event): print('main window show event') # 0延迟定时器,确保事件循环启动后再创建任务 QTimer.singleShot(0, self._start_async_task) event.accept() def _start_async_task(self): asyncio.create_task(hello()) async def main(): print('app starting') app = QApplication(sys.argv) main = MainWindow() main.show() loop = QEventLoop(app) with loop: loop.run_forever() if __name__ == '__main__': asyncio.run(main())
方法3:使用qasync的异步槽装饰器
利用qasync提供的asyncSlot装饰器,直接将Qt事件转换为异步函数,确保任务在QEventLoop中执行:
from qasync import asyncSlot class MainWindow(QMainWindow): # ... 其他初始化代码 ... def showEvent(self, event): print('main window show event') self._start_async_task() event.accept() @asyncSlot() async def _start_async_task(self): await hello()
关键注意点
- 必须确保asyncio任务是在qasync的QEventLoop成为当前活跃循环后创建,否则任务会绑定到旧的临时循环,无法及时执行。
- 不要在
__init__或showEvent的同步执行阶段直接创建asyncio任务,需等待QEventLoop启动完成。
内容的提问来源于stack exchange,提问作者iamthebull
相关产品推荐
相关产品推荐

