【问题标题】:Python 3.4 asyncio task doesn't get fully executedPython 3.4 asyncio 任务未完全执行
【发布时间】:2025-12-14 11:45:01
【问题描述】:

我正在试验 Python 3.4 的 asyncio 模块。由于没有使用 asyncio 的 MongoDB 生产就绪包,我编写了一个小型包装器类,它在执行器中执行所有 mongo 查询。这是包装器:

import asyncio
from functools import wraps
from pymongo import MongoClient


class AsyncCollection(object):
    def __init__(self, client):
        self._client = client
        self._loop = asyncio.get_event_loop()

    def _async_deco(self, name):
        method = getattr(self._client, name)

        @wraps(method)
        @asyncio.coroutine
        def wrapper(*args, **kwargs):
            print('starting', name, self._client)
            r = yield from self._loop.run_in_executor(None, method, *args, **kwargs)
            print('done', name, self._client, r)
            return r

        return wrapper

    def __getattr__(self, name):
        return self._async_deco(name)


class AsyncDatabase(object):
    def __init__(self, client):
        self._client = client
        self._collections = {}


    def __getitem__(self, col):
        return self._collections.setdefault(col, AsyncCollection(self._client[col]))


class AsyncMongoClient(object):
    def __init__(self, host, port):
        self._client = MongoClient(host, port)
        self._loop = asyncio.get_event_loop()
        self._databases = {}

    def __getitem__(self, db):
        return self._databases.setdefault(db, AsyncDatabase(self._client[db]))

我想异步执行插入,这意味着执行它们的协程不想等待执行完成。 asyncio 手册指出A task is automatically scheduled for execution when it is created. The event loop stops when all tasks are done.,所以我构建了这个测试脚本:

from asyncdb import AsyncMongoClient
import asyncio

@asyncio.coroutine
def main():
    print("Started")
    mongo = AsyncMongoClient("host", 27017)
    asyncio.async(mongo['test']['test'].insert({'_id' : 'test'}))
    print("Done")

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

当我运行脚本时,我得到以下结果:

Started
Done
starting insert Collection(Database(MongoClient('host', 27017), 'test'), 'test')

应该有一行表明 mongo 查询已完成。当我yield from 这个协程而不是使用asyncio.async 运行它时,我可以看到那条线。但是,真正奇怪的是,当我使用asyncio.async 运行此协同计算时,测试条目实际上存在于 MongoDB 中,因此尽管它似乎有效,但我不明白为什么我看不到 print 语句表明查询已执行。尽管我使用run_until_completed 运行事件循环,但它应该等待插入任务完成,即使主协程之前完成。

【问题讨论】:

    标签: python python-3.x python-asyncio


    【解决方案1】:

    asyncio.async(mongo...)) 只是 安排 mongo 查询。而run_until_complete() 不会等待它。这是使用asyncio.sleep() coroutine 的代码示例:

    #!/usr/bin/env python3
    import asyncio
    from contextlib import closing
    from timeit import default_timer as timer
    
    @asyncio.coroutine
    def sleep_BROKEN(n):
        # schedule coroutine; it runs on the next yield
        asyncio.async(asyncio.sleep(n))
    
    @asyncio.coroutine
    def sleep(n):
        yield from asyncio.sleep(n)
    
    @asyncio.coroutine
    def double_sleep(n):
        f = asyncio.async(asyncio.sleep(n))
        yield from asyncio.sleep(n) # the first sleep is also started
        yield from f
    
    n = 2
    with closing(asyncio.get_event_loop()) as loop:
        start = timer()
        loop.run_until_complete(sleep_BROKEN(n))
        print(timer() - start)
        loop.run_until_complete(sleep(n))
        print(timer() - start)
        loop.run_until_complete(double_sleep(n))
        print(timer() - start)
    

    输出

    0.0001221800921484828
    2.002586881048046
    4.005100341048092
    

    输出显示run_until_complete(sleep_BROKEN(n)) 在不到 2 毫秒而不是 2 秒内返回。 run_until_complete(sleep(n)) 可以正常工作:它会在 2 秒内返回。 double_sleep() 表明由async.async() 调度的协程在yield from 上运行(两个并发睡眠是并行的),即它睡眠2 秒,而不是4 秒。如果您在之前添加延迟(不允许事件循环运行)第一个yield from 然后你会看到yield from f 不会很快返回,即asyncio.async 不会运行协程;它只安排它们运行。

    【讨论】: