I'm new to asynchronous programming in python and I'm trying to write a script that starts a websocket server, listens for messages, and also sends messages when certain events (e.g. pressing the 's' key) are triggered in a gtk window. Here's what I have so far:
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
import asyncio
import websockets
import threading
ws = None
async def consumer_handler(websocket, path):
global ws
ws = websocket
await websocket.send("Hello client")
while True:
message = await websocket.recv()
print("Message from client: " + message)
def keypress(widget,event):
global ws
if event.keyval == 115 and ws: #s key pressed, connection open
asyncio.get_event_loop().create_task(ws.send("key press"))
print("Message to client: key press")
def quit(widget):
Gtk.main_quit()
window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
window.connect("destroy", quit)
window.connect("key_press_event", keypress)
window.show()
start_server = websockets.serve(consumer_handler, 'localhost', 8765)
asyncio.get_event_loop().run_until_complete(start_server)
wst = threading.Thread(target=asyncio.get_event_loop().run_forever)
wst.daemon = True
wst.start()
Gtk.main()
And here's the client webpage:
<!DOCTYPE html>
<html>
<head>
<title>Websockets test page</title>
<meta charset="UTF-8" />
<script>
var exampleSocket = new WebSocket("ws://localhost:8765");
function mylog(msg) {
document.getElementById("log").innerHTML += msg + "<br/>";
}
function send() {
mylog("Message to server: Hello server");
exampleSocket.send("Hello server");
}
exampleSocket.onopen = function (event) {
mylog("Connection opened");
};
exampleSocket.onmessage = function (event) {
mylog("Message from server: " + event.data);
}
</script>
</head>
<body>
<p id="log"></p>
<input type="button" value="Send message" onclick="send()"/>
</body>
</html>
Run the python code, then load the webpage in a browser, now any messages the browser sends show up in the python stdout, so far so good. But if you hit the 's' key in the gtk window, python doesn't send the message until another message is received from the browser (from pressing the 'Send message' button). I thought that await websocket.recv()
was meant to return control to the event loop until a message was received? How do I get it to send messages while it's waiting to receive?
But if you hit the 's' key in the gtk window, python doesn't send the message until another message is received from the browser
The problem is in this line:
asyncio.get_event_loop().create_task(ws.send("key press"))
Since the asyncio event loop and the GTK main loop are running in different threads, you need to use run_coroutine_threadsafe
to submit the coroutine to asyncio. Something like:
asyncio.run_coroutine_threadsafe(ws.send("key press"), loop)
create_task
adds the coroutine to the queue of runnable coroutines, but fails to wake up the event loop, which is why your coroutine is only run when something else happens in asyncio. Also, create_task
is not thread-safe, so calling it while the event loop itself is modifying the run queue could corrupt its data structures. run_coroutine_threadsafe
has neither of these problems, it arranges for the event loop to wake up as soon as possible, and it uses a mutex to protect the event loop's data structures.