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