You need to enable JavaScript to run this app. 如何使用Asyncio并发运行Dash(Flask)服务与自定义协程
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Asyncio并发运行Dash(Flask)服务与自定义协程

Dash 与 Asyncio 协程并发实现

当前实现的核心卡点:Dash 基于 Flask 提供的run_server是同步阻塞式的WSGI服务,哪怕把它包装成async异步函数,代码执行到这行时会直接接管整个线程,完全不会把控制权交还给asyncio事件循环,自然会卡死所有其他协程的执行。

不需要从零改造Dash本身,以下两种可落地的方案都能实现main业务协程和Dash服务的并发运行:

方案1:Dash服务跑在独立工作线程(改造成本最低,稳定性最高)

这是生产环境最常用的适配方式,不需要修改Dash本身的任何代码,利用asyncio自带的线程池接口把同步服务和主事件循环解耦:

  • 把原来的async def run_dashboard()改回普通同步函数,因为它会运行在独立线程,不需要被asyncio调度
  • 用asyncio.to_thread把Dash服务的启动逻辑丢到独立线程运行,这部分逻辑完全不会阻塞主事件循环
  • 用asyncio.gather同时启动Dash线程任务和main业务协程

适配后的代码示例:

import asyncio
# 原有业务逻辑完全保留
async def main():
    # 原有代码不变
    while True:
        try:
            await client.handle_message()
        except ConnectionClosedError as error:
            logger.error(error)
    
        for strategy in strategies:
            await asyncio.create_task(...)

# 改成同步函数,适配线程运行
def run_dashboard():
    app = create_app()
    # 必须关闭use_reloader,否则Flask的重载机制会启动子进程,和线程模型冲突
    app.run_server('0.0.0.0', 5000, debug=False, use_reloader=False)

if __name__ == '__main__':
    # 原有初始化代码不变
    async def run_all():
        # 并发启动Dash线程和主业务协程
        await asyncio.gather(
            asyncio.to_thread(run_dashboard),
            main()
        )
    asyncio.run(run_all())

这个方案的优势:

  • 同进程内的内存数据完全共享,Dash的回调可以直接读取main协程里采集、计算的所有数据,不需要额外的跨进程通信
  • 完全兼容已写好的Dash布局、回调代码,不需要做异步改造
  • 独立线程的服务异常不会直接拖垮主业务协程,稳定性更好

方案2:Dash服务适配为ASGI异步服务(完全融入asyncio生态)

如果你不想引入独立线程,想让所有任务都跑在同一个asyncio事件循环里,可以把同步的Flask/Dash应用通过适配器转成异步ASGI应用,配合uvicorn启动。
需要额外安装asgiref、uvicorn两个依赖,之后修改run_dashboard为真正的异步协程:

from asgiref.wsgi import WsgiToAsgi
import uvicorn

async def run_dashboard():
    app = create_app()
    # 把同步WSGI的Dash应用转换为ASGI异步应用
    asgi_app = WsgiToAsgi(app.server)
    server = uvicorn.Server(
        uvicorn.Config(
            app=asgi_app,
            host="0.0.0.0",
            port=5000,
            log_level="info"
        )
    )
    # 异步启动服务,控制权会正常交回事件循环
    await server.serve()

这种方式下,入口代码可以直接通过gather并发启动两个协程,不需要独立线程:

if __name__ == '__main__':
    asyncio.run(
        asyncio.gather(
            run_dashboard(),
            main()
        )
    )

常见踩坑提示

  • 不要直接把app.run_server放在async函数里调用:哪怕你给函数加了async标识,内部的同步阻塞逻辑依然会锁死整个事件循环,其他协程根本得不到执行机会
  • 如果需要在Dash的回调里触发主业务的异步任务,不要在Dash回调里直接调用asyncio.run(),需要提前把主事件循环的引用存为全局变量,通过asyncio.run_coroutine_threadsafe把任务安全投递回主循环执行,避免跨线程调用报错

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.24 15:45:05