Python websockets 和 gtk - 对 asyncio 队列感到困惑

Python websockets and gtk - confused about asyncio queue

我是 python 异步编程的新手,我正在尝试编写一个脚本来启动 websocket 服务器,侦听消息,并在某些事件(例如按下 's' 键)在 gtk window 中被触发。这是我目前所拥有的:

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

这是客户端网页:

<!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>

运行 python 代码,然后在浏览器中加载网页,现在浏览器发送的任何消息都会显示在 python stdout 中,到目前为止一切顺利。但是,如果您在 gtk window 中按下 's' 键,python 不会发送消息,直到从浏览器收到另一条消息(通过按下 'Send message' 按钮) .我认为 await websocket.recv() 是为了 return 控制事件循环直到收到消息?如何让它在等待接收时发送消息?

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

问题出在这一行:

asyncio.get_event_loop().create_task(ws.send("key press"))

由于asyncio事件循环和GTK主循环运行在不同的线程中,需要使用run_coroutine_threadsafe将协程提交给asyncio。类似于:

asyncio.run_coroutine_threadsafe(ws.send("key press"), loop)

create_task 将协程添加到 运行nable 协程的队列中,但未能唤醒事件循环,这就是为什么你的协程只有 运行 当其他事情发生时在异步中。此外,create_task 不是线程安全的,因此在事件循环本身修改 运行 队列时调用它可能会破坏其数据结构。 run_coroutine_threadsafe这两个问题都没有,它安排事件循环尽快唤醒,它使用互斥量来保护事件循环的数据结构。