Python:同步程序中的WebSocket
我有一个bog标准的同步python程序,需要能够从WebSocket读取数据并用数据更新GUI。然而,asyncio爬行经常让我绊倒 我如何制作一个模块:Python:同步程序中的WebSocket,python,websocket,python-asyncio,Python,Websocket,Python Asyncio,我有一个bog标准的同步python程序,需要能够从WebSocket读取数据并用数据更新GUI。然而,asyncio爬行经常让我绊倒 我如何制作一个模块: 接受对多个源的多个订阅 只要有数据,就向请求者发送更新 每个URL只打开一个websocket连接 如果websocket关闭,则重置websocket 以下是我已经做过的,但它在很多方面都失败了: run\u forever()意味着循环在订阅完成之前被卡住,然后handle()在循环时被卡住 它似乎不想在套接字关闭时重新启动它们,因为w
run\u forever()
意味着循环在订阅完成之前被卡住,然后handle()
在循环时被卡住
属性(没有s的websocket有,但我不清楚区别,也无法在线找到信息)
class WSClient():
subscriptions = set()
connections = {}
started = False
def __init__(self):
self.loop = asyncio.get_event_loop()
def start(self):
self.started = True
self.loop.run_until_complete(self.handle())
self.loop.run_until_forever() # problematic, because it does not allow new subscribe() events
async def handle(self):
while len(self.connections) > 0:
# listen to every websocket
futures = [self.listen(self.connections[url]) for url in self.connections]
done, pending = await asyncio.wait(futures)
# the following is apparently necessary to avoid warnings
# about non-retrieved exceptions etc
try:
data, ws = done.pop().result()
except Exception as e:
print("OTHER EXCEPTION", e)
for task in pending:
task.cancel()
async def listen(self, ws):
try:
async for data in ws:
data = json.loads(data)
# call the subscriber (listener) back when there's data
[s.listener._handle_result(data) for s in self.subscriptions if s.ws == ws]
except Exception as e:
print('ERROR LISTENING; RESTARTING SOCKET', e)
await asyncio.sleep(2)
self.restart_socket(ws)
def subscribe(self, subscription):
task = self.loop.create_task(self._subscribe(subscription))
asyncio.gather(task)
if not self.started:
self.start()
async def _subscribe(self, subscription):
try:
ws = self.connections.get(subscription.url, await websockets.connect(subscription.url))
await ws.send(json.dumps(subscription.sub_msg))
subscription.ws = ws
self.connections[subscription.url] = ws
self.subscriptions.add(subscription)
except Exception as e:
print("ERROR SUBSCRIBING; RETRYING", e)
await asyncio.sleep(2)
self.subscribe(subscription)
def restart_socket(self, ws):
for s in self.subscriptions:
if s.ws == ws and not s.ws.connected:
print(s)
del self.connections[s.url]
self.subscribe(s)
我有一个bog标准的同步python程序,需要能够从WebSocket读取数据并用数据更新GUI。然而,asyncio爬行经常让我绊倒
正如您提到的GUI,那么它可能不是“bog标准同步python程序”。通常,GUI程序有一个非阻塞事件驱动的主线程,它允许并发用户行为和回调。这与asyncio非常相似,通常asyncio与GUI一起使用GUI特定的事件循环来替换asyncio中的默认事件循环是一种常见的方式,这样您的asyncio协程就可以在GUI事件循环中运行,并且您可以避免调用run\u ever()
阻塞所有内容
另一种方法是在单独的线程中运行asyncio事件循环,以便程序可以同时等待websocket数据和用户单击。我已将您的代码改写如下:
import asyncio
import threading
import websockets
import json
class WSClient(threading.Thread):
def __init__(self):
super().__init__()
self._loop = None
self._tasks = {}
self._stop_event = None
def run(self):
self._loop = asyncio.new_event_loop()
self._stop_event = asyncio.Event(loop=self._loop)
try:
self._loop.run_until_complete(self._stop_event.wait())
self._loop.run_until_complete(self._clean())
finally:
self._loop.close()
def stop(self):
self._loop.call_soon_threadsafe(self._stop_event.set)
def subscribe(self, url, sub_msg, callback):
def _subscribe():
if url not in self._tasks:
task = self._loop.create_task(
self._listen(url, sub_msg, callback))
self._tasks[url] = task
self._loop.call_soon_threadsafe(_subscribe)
def unsubscribe(self, url):
def _unsubscribe():
task = self._tasks.pop(url, None)
if task is not None:
task.cancel()
self._loop.call_soon_threadsafe(_unsubscribe)
async def _listen(self, url, sub_msg, callback):
try:
while not self._stop_event.is_set():
try:
ws = await websockets.connect(url, loop=self._loop)
await ws.send(json.dumps(sub_msg))
async for data in ws:
data = json.loads(data)
# NOTE: please make sure that `callback` won't block,
# and it is allowed to update GUI from threads.
# If not, you'll need to find a way to call it from
# main/GUI thread (similar to `call_soon_threadsafe`)
callback(data)
except Exception as e:
print('ERROR; RESTARTING SOCKET IN 2 SECONDS', e)
await asyncio.sleep(2, loop=self._loop)
finally:
self._tasks.pop(url, None)
async def _clean(self):
for task in self._tasks.values():
task.cancel()
await asyncio.gather(*self._tasks.values(), loop=self._loop)
您可以尝试龙卷风和高速公路扭曲的websockets 谢谢。我还有一些更紧迫的事情要处理,但我会尝试你的解决方案,并让你知道它是如何工作的谢谢你的建议。至少现在它可以与服务器通信了,只是做了一个小改动:我在订阅时创建新的事件循环,而不是在运行时。否则为空。然而,它仍然以某种方式阻塞GUI,尽管WSClient在一个单独的线程中运行,我使用
GLib.idle\u add()
向GUI发送更新信号。事实上,GUI的任何部分都没有初始化,所以我仍然想知道是怎么回事。它正在打印来自websocket的响应,但GUI根本没有显示出来。这可能是因为启动ws客户端后调用了Gtk.main()
。但是,如果我之前启动它,主循环不允许运行其他循环。因此,也许你的第一个解决方案是正确的,我只是不知道如何(现在)