我正在尝试创建一个基于PyQt5和asyncio的新应用程序(使用python 3.4,期待最终使用async / await升级到3.5)。我的目标是使用asyncio,以便即使应用程序正在等待某些连接的硬件完成操作,GUI也能保持响应。
在查看如何合并Qt5和asyncio的事件循环时,我找到了mailing list posting,建议使用quamash。但是,在运行此示例(未修改)时,
yield from fut
好像似乎又回来了。我看到输出'Timeout',所以定时器回调显然会触发,但Future未能唤醒等待方法。当手动关闭窗口时,它告诉我有未完成的未来:
Yielding until signal...
Timeout
Traceback (most recent call last):
File "pyqt_asyncio_list.py", line 26, in <module>
loop.run_until_complete(_go())
File "/usr/local/lib/python3.5/site-packages/quamash/__init__.py", line 291, in run_until_complete
raise RuntimeError('Event loop stopped before Future completed.')
RuntimeError: Event loop stopped before Future completed.
我在使用python 3.5的Ubuntu和使用3.4的Windows上测试了这个,在两个平台上都有相同的行为。
无论如何,由于这不是我实际尝试实现的,我还测试了其他一些代码:
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')
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()
with quamash.QEventLoop(app=QApplication([])) as loop:
w = Example()
loop.run_forever()
#~ loop = asyncio.get_event_loop()
#~ loop.run_until_complete(slow_operation())
程序应该显示一个窗口,里面有一个按钮(它可以),按钮调用slow_operation()而不会阻止GUI。运行此示例时,我可以根据需要随时单击该按钮,因此不会阻止GUI。但是
yield from asyncio.sleep(0.1)
永远不会传递,终端输出如下所示:
btnCallback returns...
clicked
op()
op done
btnCallback returns...
clicked
op()
op done
这次关闭窗口时没有抛出异常。如果我用它直接运行事件循环,slow_operation()函数基本上有效:
#~ with quamash.QEventLoop(app=QApplication([])) as loop:
#~ w = Example()
#~ loop.run_forever()
loop = asyncio.get_event_loop()
loop.run_until_complete(slow_operation())
现在,有两个问题:
这通常是实现冗长操作与GUI解耦的明智方法吗?我的意图是按钮回调将协程调用发布到事件循环(有或没有额外的嵌套级别,参见coroCallHelper()),然后在那里进行调度和执行。我不需要单独的线程,因为它实际上只需要花费时间,没有实际处理的I / O.
如何解决此问题?
谢谢, 菲利普
答案 0 :(得分:11)
好的,这只是SO的一个优点:写下一个问题会让你再想一想。不知怎的,我只是想出来了:
再看一下{{3}}的例子,我发现要使用的事件循环有所不同:
RET=1
while [[ RET -ne 0 ]]; do
echo "=> Waiting for confirmation of MongoDB service startup"
sleep 5
mongo admin --eval "help" >/dev/null 2>&1
RET=$?
done
关键似乎是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())
。同样重要的是要注意,asyncio.set_event_loop()
提到的是来自quamash包的那个,而不是来自Qt5。所以我的例子现在看起来像这样:
QEventLoop
现在它'正常工作':
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')
也许这对其他人有所帮助。我至少对它很满意;) 当然,仍然欢迎对一般模式的评论!
此致 菲利普