You need to enable JavaScript to run this app. 使用asyncio开发WebSocket连接器,手动调用可获取行情但加while循环后数据为空?
优惠活动
大模型
产品
解决方案
定价
更多

使用asyncio开发WebSocket连接器,手动调用可获取行情但加while循环后数据为空?

问题根因

你猜测的方向完全正确,问题核心是同步while循环的阻塞直接卡死了整个异步事件循环,完整逻辑链如下:

  1. Jupyter环境本身内置了常驻运行的asyncio事件循环,你调用asyncio.get_event_loop()拿到的就是这个正在调度任务的循环,start()创建的websocket连接协程会被丢进循环的待执行队列,由后台循环调度执行。所以你手动切换单元格执行get_quotes()的时候,事件循环一直保持运行,websocket能正常收消息、更新行情,读出来的数据是正常的。
  2. 当你运行自己写的同步while(True) + time.sleep(3)代码时,time.sleep是完全阻塞线程的系统调用,会直接占死主线程,asyncio事件循环根本拿不到CPU执行权——这时候你之前创建的websocket连接协程连建立连接、发订阅消息、接收行情的机会都没有,自然quotes一直是空的。

原代码还有几个附带的缺陷,在非Jupyter场景下会直接导致功能失效:

  • 非Jupyter环境下只调用create_task创建协程任务,但从来没有让事件循环持续运行,整个WS连接协程根本不会被执行,程序直接就退出了。
  • __init__里传入的client参数没有被任何逻辑使用,属于冗余参数。
  • 没有任何异常处理和重连逻辑,公网WS连接一旦因为网络抖动、服务端策略断连,程序就会静默失去数据。
  • 直接高频修改空DataFrame的第0行,在每秒几十上百条的高频行情流下,性能开销极高,会直接拖慢整个事件循环。
修正方案

不要把同步阻塞逻辑和异步事件循环混在同一条执行路径上,把所有轮询读数据的逻辑也改成协程,交给事件循环统一调度。改造后的完整代码如下:

import pandas as pd
import json
import websockets
import asyncio

class BinanceQuotesWS:    
    def __init__(self, pair):
        # 用原生变量存最新行情,避免高频修改DataFrame的性能损耗
        self.last_quote = {"Timestamp": None, "Price": None}
        self.pair = pair
        self.socket = 'wss://fstream.binance.com/ws'
        self.websocket = None
        self.is_running = False

    def get_quotes(self):
        # 需要返回DataFrame结构的时候再临时生成
        return pd.DataFrame([self.last_quote])

    async def connect(self):
        """建立WS连接,内置异常重连逻辑"""
        self.is_running = True
        while self.is_running:
            try:
                self.websocket = await websockets.connect(self.socket)
                await self.subscribe_quotes()
            except Exception:
                # 连接断开后等2秒重连,避免频繁请求触发服务端限流
                await asyncio.sleep(2)

    async def subscribe_quotes(self):
        """发送订阅并持续接收行情"""
        subscribe_message = {
            "method": "SUBSCRIBE",
            "params": [self.pair.lower()+"@trade"],
            "id": 1
        }
        await self.websocket.send(json.dumps(subscribe_message))
        async for msg in self.websocket:
            msg = json.loads(msg)    
            if 'p' in msg:
                # 直接更新原生变量,不触碰DataFrame结构
                self.last_quote["Timestamp"] = msg['E']
                self.last_quote["Price"] = float(msg['p'])

    async def poll_quotes(self, interval=3):
        """异步轮询行情逻辑,替代原来的同步while循环"""
        while self.is_running:
            quotes = self.get_quotes()
            print(quotes)
            # 用asyncio.sleep替代time.sleep,不会阻塞事件循环调度
            await asyncio.sleep(interval)

    def start(self):
        """启动入口:把WS连接、行情轮询逻辑注册到事件循环并发执行"""
        async def main():
            # 两个协程并行运行,事件循环会自动交替调度,不会互相阻塞
            task_ws = asyncio.create_task(self.connect())
            task_poll = asyncio.create_task(self.poll_quotes())
            await asyncio.gather(task_ws, task_poll)
        asyncio.run(main())

使用方式非常简单,直接启动即可,不需要额外写同步循环:

temp_ws = BinanceQuotesWS(pair='BTCUSDT')
temp_ws.start()
异步场景通用优化建议
  • 绝对禁止在异步事件循环的执行路径上调用同步阻塞方法:包括time.sleep、requests库的HTTP请求、无异步版本的数据库操作、重计算的pandas逻辑,这类调用一执行就会把整个事件循环卡死,所有异步任务都会停摆。如果一定要运行同步逻辑,必须丢到asyncio.to_thread中异步执行,不要占用事件循环的主线程。
  • 高频写入场景不要频繁修改大体积可变对象:WS行情流是典型的高频写入场景,每次给DataFrame新增/修改行都会触发内存重新分配,性能开销远大于用原生字典、列表存数据的方式,只有在需要做数据分析、输出结构化结果的时候再转成DataFrame。
  • 公网WS连接必须加自动重连机制:网络抖动、服务端主动断连都是常态,没有重连逻辑的连接器运行几分钟就会静默失去数据。
  • 非Jupyter环境下统一用asyncio.run()作为事件循环的启动入口,不要依赖老旧的asyncio.get_event_loop()方法,避免跨环境的兼容性问题。

内容的提问来源于stack exchange,提问作者romanzh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.24 19:54:01