PyQt5和asyncio:yield from never finishes

2024-10-05 14:21:59 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在尝试创建一个基于PyQt5和asyncio的新应用程序(使用python3.4,希望最终使用async/await升级到3.5)。我的目标是使用asyncio,这样即使在应用程序等待连接的硬件完成操作时,GUI也能保持响应。在

在研究如何合并Qt5和asyncio的事件循环时,我发现了一个mailing list posting,建议使用quamash。但是,在运行此示例(未修改)时

yield from fut

似乎再也不会回来了。我看到输出'Timeout',因此timer回调显然会触发,但是未来无法唤醒waiting方法。当手动关闭窗口时,它告诉我存在未完成的期货:

^{pr2}$

我用python3.5在Ubuntu上测试了这一点,在Windows上测试了3.4,两个平台上的行为相同。在

总之,由于这不是我真正要实现的目标,我还测试了其他一些代码:

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())

现在,有两个问题:

  1. 一般来说,这是实现长时间操作与GUI分离的明智方法吗?我的意图是按钮回调将协同例程调用发布到事件循环(有或没有额外的嵌套级别,cf.coroCallHelper()),然后在那里调度和执行。我不需要单独的线程,因为实际上只有I/O需要时间,没有实际的处理。

  2. 我怎样才能纠正这种行为?

谢谢, 菲利普


Tags: fromimportselfloopasynciodefguioperation
1条回答
网友
1楼 · 发布于 2024-10-05 14:21:59

好吧,这是一个优点:写下一个问题会让你重新思考每件事。不知怎么的,我才明白:

再次查看quamash repo中的示例,我发现要使用的事件循环的获取方式有些不同:

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()。还需要注意的是,这里提到的QEventLoop是来自quamash包的,而不是Qt5。我的例子如下:

^{pr2}$

它现在“管用”了:

btnCallback returns...
clicked
op()
op done
timeout expired
second timeout expired
Coroutine has ended

也许这对其他人有些帮助。至少我很满意;) 当然,对一般模式的评论仍然是受欢迎的!在

附录:请注意,如果quamash被asyncqt取代,那么这可以用于python3.7.x之前的最新Python版本。但是,在python3.8中使用相同的代码会导致@coroutine装饰器生成RuntimeWarnings,并最终在asyncio.sleep()中出现{}失败。也许其他人知道该怎么做才能让它再次工作。可能只是asyncqt还不兼容python3.8。在

谨致问候, 菲利普

相关问题 更多 >