【问题标题】:How do I add asyncio task to pyqt5 event loop so that it runs and avoids the never awaited error?如何将 asyncio 任务添加到 pyqt5 事件循环中,以便它运行并避免从未等待的错误?
【发布时间】:2021-04-18 19:23:05
【问题描述】:

我是 asyncio 的新手,我想在我的 pyqt 应用程序中利用它来处理通过 tcp 连接进行的通信。

我做了这个简单的演示来学习如何在 asyncio 上下文中处理 QT 循环。我看过与此相关的其他帖子,但它们比我目前尝试做的要复杂得多。因此,我在一个单独的窗口中启动服务器客户端,以便它进行侦听,并尝试通过我的小部件上的简单按钮单击事件发送消息。尽可能简单......我的问题是它不起作用。

我希望能够进行单次信息交换以及端口为流打开的情况。我认为这些任务在 asyncio 中是直截了当的,但在这一点上让它与 qt 一起玩似乎很困难。

现在我得到了

RuntimeWarning: coroutine 'PushButton.sendmessage' was never awaited
  rslt = self.__app.exec_()
RuntimeWarning: Enable tracemalloc to get the object allocation traceback

我不知道从哪里着手解决这个问题。

test.py

from PyQt5.QtWidgets import QApplication, QWidget, QPushButton
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import Qt, pyqtSlot
import sys
import asyncio
from asyncqt import QEventLoop
from async_client import tcp_echo_client


class PushButton(QWidget):

    loop = None

    def __init__(self,app_loop):
        super(PushButton,self).__init__()
        self.initUI()
        self.loop = loop

    def initUI(self):
        self.setWindowTitle("PushButton")
        self.setGeometry(400,400,300,260)
        self.send_bttn = QPushButton(self)
        self.send_bttn.setText("SEnd Message")      
        self.send_bttn.clicked.connect(self.sendmessage)
        self.send_bttn.move(100,100)
        
    @pyqtSlot()    
    async def sendmessage(self):
        run_command = asyncio.create_task(tcp_echo_client("hey",self_loop))
        asyncio.run(run_command)



if __name__ == '__main__':
    app = QApplication(sys.argv)
    loop = QEventLoop(app)
    asyncio.set_event_loop(loop)  # NEW must set the event loop    sys.exit(app.exec_()) 
    
    ex = PushButton(loop)
    ex.show()
    with loop:
        loop.run_forever()

简单的回显客户端例程

import asyncio


async def tcp_echo_client(message, loop):
    reader, writer = await asyncio.open_connection('127.0.0.1', 8889,
                                                   loop=loop)

    print('Send: %r' % message)
    writer.write(message.encode())

    data = await reader.read(100)
    print('Received: %r' % data.decode())

    print('Close the socket')
    writer.close()

和响应服务器


import asyncio

async def handle_echo(reader, writer):
    data = await reader.read(100)
    message = data.decode()
    addr = writer.get_extra_info('peername')
    print("Received %r from %r" % (message, addr))

    print("Send: %r" % message)
    writer.write(data)
    await writer.drain()

    print("Close the client socket")
    writer.close()

loop = asyncio.get_event_loop()
coro = asyncio.start_server(handle_echo, '127.0.0.1', 8889, loop=loop)
server = loop.run_until_complete(coro)

# Serve requests until Ctrl+C is pressed
print('Serving on {}'.format(server.sockets[0].getsockname()))
try:
    loop.run_forever()
except KeyboardInterrupt:
    pass

# Close the server
server.close()
loop.run_until_complete(server.wait_closed())
loop.close()

【问题讨论】:

    标签: python tcp pyqt5 python-asyncio coroutine


    【解决方案1】:

    问题是,如果你调用一个作为协程的槽,那么你必须使用asyncSlot装饰器,也不要使用ayncion.run()而是await(除了消除其他错别字)。

    import sys
    import asyncio
    
    from PyQt5.QtWidgets import QApplication, QWidget, QPushButton
    
    from asyncqt import QEventLoop, asyncSlot
    from async_client import tcp_echo_client
    
    
    class PushButton(QWidget):
    
        loop = None
    
        def __init__(self, loop=None):
            super(PushButton, self).__init__()
            self.initUI()
            self.loop = loop or asyncio.get_event_loop()
    
        def initUI(self):
            self.setWindowTitle("PushButton")
            self.setGeometry(400, 400, 300, 260)
            self.send_bttn = QPushButton(self)
            self.send_bttn.setText("SEnd Message")
            self.send_bttn.clicked.connect(self.sendmessage)
            self.send_bttn.move(100, 100)
    
        @asyncSlot()
        async def sendmessage(self):
            await tcp_echo_client("hey", self.loop)
    
    
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        loop = QEventLoop(app)
        asyncio.set_event_loop(loop)
    
        ex = PushButton(loop)
        ex.show()
        with loop:
            loop.run_forever()
    

    【讨论】:

    • 另外,x = asyncio.create_task(...) 后跟await x 是多余的,它可能只是await tcp_echo_client(...)
    • @user4815162342 这就是我的想法,但我不想改变太多不是导致错误的部分,感谢您帮助我改进我的帖子。
    • 不客气,没问题。请注意,我对 Qt 与 asyncio 的集成知之甚少(或实际上一无所知),因此我无法判断这两种变体是否能正常工作 - 例如,是否可以等待可能长时间运行的 tcp_echo_client 来自异步插槽?此外,您在 sendmessage 中还有一个 = 是上一版本遗留下来的。
    • @user4815162342 这正是 asyncSlot 所做的:它在被调用时启动一个任务,连接到信号的是协程包装器。
    • @eyllanesc 为什么我不将循环传递到表单中。我注意到你用self.loop = loop or asyncio.get_event_loop()删除了它
    猜你喜欢
    • 1970-01-01
    • 2021-06-21
    • 1970-01-01
    • 2021-05-21
    • 1970-01-01
    • 2016-06-25
    • 2018-10-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多