我是python中异步编程的新手,我正在尝试编写一个启动websocket服务器的脚本,监听消息,并在某些事件(例如按下's'键)被触发时发送消息一个gtk窗口。这是我到目前为止所做的:
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标准输出中,到目前为止一直很好。但是如果你点击gtk窗口中的's'键,python就不会发送消息,直到从浏览器收到另一条消息(按下'发送消息'按钮)。我认为await websocket.recv()
是为了将控制权返回给事件循环,直到收到消息为止?如何在等待接收时发送消息?
答案 0 :(得分:2)
但是,如果你点击了&#39;在gtk窗口中键,python不会发送消息,直到从浏览器收到另一条消息
问题出在这一行:
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
将协同程序添加到可运行协同程序的队列中,但无法唤醒事件循环,这就是为什么只有在asyncio中发生其他事件时才运行协程的原因。此外,create_task
不是线程安全的,因此在事件循环本身修改运行队列时调用它可能会破坏其数据结构。 run_coroutine_threadsafe
没有这些问题,它安排事件循环尽快唤醒,并使用互斥锁来保护事件循环的数据结构。