You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python asyncio协程二次返回GraphQL查询结果时触发RuntimeError

问题

我编写了一个GraphQLLibrary类,核心功能是创建GraphQL客户端、异步发送GraphQL请求(查询/变更)并返回结果。后续需要支持订阅和触发订阅的变更,因此请求必须异步执行。

首次创建客户端并发送查询一切正常,但第二次发送查询时,虽然在send_graphql_request_coroutine内已获取到结果,但返回时触发RuntimeError,错误信息:

RuntimeError: <asyncio.locks.Event object at 0x00000191E01F55D0 [unset]> is bound to a different event loop

我原以为第一次调用asyncio.run(...)后事件循环会关闭,无法理解该错误原因。

代码示例

from gql import gql, Client
from gql.transport.websockets import WebsocketsTransport
import asyncio


class GraphQLLibrary:
    def __init__(self):
        self.client = None

    def send_graphql_request(self, query): 
        query_result = asyncio.run(self.send_graphql_request_coroutine(query))
        print("===== received query result: ", query_result)
        return query_result

    def create_graphql_client(self, url):
        transport = WebsocketsTransport(url)
        self.client = Client(transport=transport)

    async def send_graphql_request_coroutine(self, query):
        if not self.client: 
            raise AssertionError("GraphQL client is not initialized. Call 'Create GraphQL Client' first.")
        
        async with self.client as session:      
            query = gql(query)

            query_result = await session.execute(query)          
            print("===== got query result:", query_result, "\n===== returning ...")
            return query_result


url = "ws://URL/"

query = """query { system { version }}"""

gql_lib = GraphQLLibrary()

gql_lib.create_graphql_client(url)
gql_lib.send_graphql_request(query)     # 第一次请求正常
gql_lib.send_graphql_request(query)     # 第二次请求返回结果时出错

终端错误输出

===== got query result: {'system': {'version': 'SR355.SC42.D.24090913'}} 
===== returning ...
===== received query result:  {'system': {'version': 'SR355.SC42.D.24090913'}}
===== got query result: {'system': {'version': 'SR355.SC42.D.24090913'}} 
===== returning ...
Traceback (most recent call last):
  File "c:\Users\User\directory\test_gqlLib.py", line 55, in <module>
    gql_lib.send_graphql_request(query)     # this raises an error, when returning the query result
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Users\User\directory\test_gqlLib.py", line 19, in send_graphql_request
    query_result = asyncio.run(self.send_graphql_request_coroutine(query))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Program Files\Python311\Lib\asyncio\runners.py", line 190, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "C:\Program Files\Python311\Lib\asyncio\runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Program Files\Python311\Lib\asyncio\base_events.py", line 650, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "c:\Users\User\directory\test_gqlLib.py", line 34, in send_graphql_request_coroutine
    async with self.client as session:
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\User\directory\.venv\Lib\site-packages\gql\client.py", line 662, in __aexit__
    await self.close_async()
  File "C:\Users\User\directory\.venv\Lib\site-packages\gql\client.py", line 654, in close_async
    await self.transport.close()
  File "C:\Users\User\directory\.venv\Lib\site-packages\gql\transport\websockets_base.py", line 660, in close
    await self.wait_closed()
  File "C:\Users\User\directory\.venv\Lib\site-packages\gql\transport\websockets_base.py", line 667, in wait_closed
    await self._wait_closed.wait()
  File "C:\Program Files\Python311\Lib\asyncio\locks.py", line 210, in wait
    fut = self._get_loop().create_future()
          ^^^^^^^^^^^^^^^^
  File "C:\Program Files\Python311\Lib\asyncio\mixins.py", line 20, in _get_loop
    raise RuntimeError(f'{self!r} is bound to a different event loop')
RuntimeError: <asyncio.locks.Event object at 0x00000191E01F55D0 [unset]> is bound to a different event loop

原因分析

问题根源在于asyncio.run()的特性与WebsocketsTransport的内部实现冲突:

  • 每次调用asyncio.run()都会创建一个全新的事件循环,执行完毕后自动关闭该循环。
  • 第一次请求时,WebsocketsTransport内部的_wait_closed事件对象绑定了第一次的事件循环。
  • 第二次调用asyncio.run()会创建新的事件循环,但客户端复用了之前的WebsocketsTransport实例,当请求结束关闭客户端时,尝试在新循环中等待绑定旧循环的_wait_closed事件,导致循环不匹配的错误。

尽管第一次请求后事件循环已关闭,但WebsocketsTransport实例并未被销毁,其内部事件对象仍保留对旧循环的引用。

解决方案

方案1:复用同一个事件循环

修改GraphQLLibrary类,初始化时创建并保留一个持久化的事件循环,所有请求都在该循环中执行:

from gql import gql, Client
from gql.transport.websockets import WebsocketsTransport
import asyncio


class GraphQLLibrary:
    def __init__(self):
        self.client = None
        self.loop = asyncio.new_event_loop()  # 创建持久化事件循环

    def send_graphql_request(self, query): 
        query_result = self.loop.run_until_complete(self.send_graphql_request_coroutine(query))
        print("===== received query result: ", query_result)
        return query_result

    def create_graphql_client(self, url):
        transport = WebsocketsTransport(url)
        self.client = Client(transport=transport)

    async def send_graphql_request_coroutine(self, query):
        if not self.client: 
            raise AssertionError("GraphQL client is not initialized. Call 'Create GraphQL Client' first.")
        
        async with self.client as session:      
            query = gql(query)
            query_result = await session.execute(query)          
            print("===== got query result:", query_result, "\n===== returning ...")
            return query_result

    def close(self):
        # 程序结束时手动关闭事件循环
        self.loop.close()


# 使用示例
url = "ws://URL/"
query = """query { system { version }}"""

gql_lib = GraphQLLibrary()
gql_lib.create_graphql_client(url)
gql_lib.send_graphql_request(query)
gql_lib.send_graphql_request(query)
gql_lib.close()  # 最后关闭循环

方案2:每次请求重新创建客户端

如果不需要长期保持连接,可每次发送请求前重新创建客户端,避免复用绑定旧循环的Transport实例:

def send_graphql_request(self, query, url): 
    # 每次请求重新创建客户端
    self.create_graphql_client(url)
    query_result = asyncio.run(self.send_graphql_request_coroutine(query))
    print("===== received query result: ", query_result)
    return query_result

# 使用示例
gql_lib.send_graphql_request(query, url)
gql_lib.send_graphql_request(query, url)

方案3:全异步风格改造(推荐,适配后续订阅功能)

既然后续要支持订阅,采用全异步写法更合理,避免同步代码嵌套异步调用:

from gql import gql, Client
from gql.transport.websockets import WebsocketsTransport
import asyncio


class GraphQLLibrary:
    def __init__(self):
        self.client = None

    async def create_graphql_client(self, url):
        transport = WebsocketsTransport(url)
        self.client = Client(transport=transport)

    async def send_graphql_request(self, query): 
        if not self.client: 
            raise AssertionError("GraphQL client is not initialized. Call 'create_graphql_client' first.")
        
        async with self.client as session:      
            query = gql(query)
            query_result = await session.execute(query)          
            print("===== got query result:", query_result, "\n===== returning ...")
            return query_result


# 使用示例
async def main():
    url = "ws://URL/"
    query = """query { system { version }}"""

    gql_lib = GraphQLLibrary()
    await gql_lib.create_graphql_client(url)
    result1 = await gql_lib.send_graphql_request(query)
    print("===== received query result: ", result1)
    result2 = await gql_lib.send_graphql_request(query)
    print("===== received query result: ", result2)

asyncio.run(main())

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 05:29:50