2

I am new to asyncio and I want to leverage it for my pyqt application to handle communication over a tcp connection.

I made this trivial demo to learn how to deal with the QT loop in asyncio context. I have seen other post related to this but they are much more complicated than what I am trying to do at the moment. So I start the server client in a separate window so it listens and I try to send a message through my simple button click event on my widget. As barebones as it gets.... My problem is it does not work.

I am looking to be able to a single exchange of info and a case where the port is left open for a stream. I think these tasks would be straight forward in asyncio but having it play nice with qt seems difficult at this point.

right now I am getting

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

I am not sure where to start on fixing this.

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

the simple echo client routine

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

and the responding server


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

1 Answer 1

7

The problem is that if you invoke a slot that is a coroutine then you must use the asyncSlot decorator, also do not use ayncion.run() but await(In addition to eliminating other typos).

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()
Sign up to request clarification or add additional context in comments.

5 Comments

Also, x = asyncio.create_task(...) followed by await x is superfluous, it could just be await tcp_echo_client(...).
@user4815162342 That is what I was thinking but I did not want to change much the part that was not the cause of the error, thanks for helping me improve my post.
You're welcome, no problem. Note that I don't know enough (or really anything) about Qt's integration with asyncio, so I can't tell whether either variant will work correctly - for example, is it ok to await the potentially long-running tcp_echo_client from an async slot? Also you have a = in sendmessage left over from the previous version.
@user4815162342 This is exactly what asyncSlot does: it launches a task when it is invoked, what connects to the signal is an coroutine wrapper.
@eyllanesc why dont I pass in the loop into the form. I noticed that you took it out with self.loop = loop or asyncio.get_event_loop()

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.