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

不依赖asyncio,基于generator实现异步编程是否正确?

问题

我希望深入了解异步编程及其内部机制,据我所知asyncio的核心是生成器。我尝试不使用asyncio来异步执行worker1和worker2,请问这段代码是否真的实现了异步?这是一个简易实现,我的研究方向是否正确?

实现代码

import typing
import socket

def make_http_request(
    *,
    host: str = 'www.google.com',
    port: int = 80,
    worker_name: str,
    return_value: typing.Any
):
    '''
    Implement this method to read HTTP response chunk by chunk (requests.get() blocks whole thread !)
    '''
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client.connect((host, port))
    request_string = f'GET / HTTP/1.1\r\nHost: {host}\r\n\r\n'
    client.send(request_string.encode())
    response_string = b''
    while True:
        response_string += client.recv(20)
        if response_string.endswith(b'\r\n\r\n'):
            break
        yield response_string
    return return_value

def async_mock_implementation(*args, **kwargs):
    '''
    generator.send(value)
        Resumes the execution and “sends” a value into the generator function. The value argument becomes the result of the current yield expression. 
        The send() method returns the next value yielded by the generator, or raises StopIteration if the generator exits without yielding another value. 
        When send() is called to start the generator, it must be called with None as the argument, because there is no yield expression that could receive the value.
    '''
    tasks_mapping: typing.Dict[str, typing.Generator] = {
        'worker1': make_http_request(worker_name='worker1', return_value=['<worker1> value']),
        'worker2': make_http_request(worker_name='worker2', return_value=['<worker2> value'])
    }
    tasks_result: typing.Dict[str, None] = dict.fromkeys(list(tasks_mapping.keys()), None)
    while True:
        keys = list(tasks_mapping.keys())
        if not keys:
            break
        for k in keys.copy():
            # Start switching between tasks ! `k` represents reference on the task !
            print(k)
            try:
                value = tasks_mapping[k].send(None) # We are doing this to wake up the generator !
            except StopIteration as exc:
                tasks_result[k] = exc.value # Once <class 'generator'> raises a `StopIteration`, returned value from `generator function` will be set on `StopIteration's` `value` attribute. 
                del tasks_mapping[k]
    print(tasks_result)

def main():
    async_mock_implementation()

if __name__ == '__main__':
    main()
回答

1. 研究方向完全正确

Python早期的asyncio确实基于生成器实现(比如asyncio.coroutine装饰器最初就是用来包装生成器函数),你手动实现生成器+调度器的思路,完全命中了协程异步模型的核心:通过用户态的任务切换,避免操作系统线程切换的开销,这正是异步IO的本质之一。

你的async_mock_implementation已经实现了一个微型事件循环的雏形:维护任务列表、轮询唤醒生成器、捕获StopIteration获取任务返回值,这部分逻辑是对的。

2. 当前代码没有实现真正的异步

虽然代码看起来有任务切换逻辑,但实际是伪异步,问题出在IO操作上:

  • make_http_request里用的是阻塞式socket调用:client.connect()和client.recv(20)都会阻塞整个线程。比如当worker1执行到recv时,如果服务器还没返回数据,整个线程会卡在这个调用上,调度器根本没机会切换到worker2。
  • 你在recv后加的yield response_string只有在recv成功拿到数据后才会执行——如果recv阻塞了,yield根本没机会触发,任务切换也就无从谈起。

3. 如何改成真正的异步?

要实现真正的异步,必须结合非阻塞IO和IO多路复用:

  • 把socket设置为非阻塞模式:client.setblocking(False)
  • 当调用connect或recv时,会抛出BlockingIOError,此时立即yield,把控制权交还给调度器
  • 调度器需要用select/poll/epoll等工具,监听所有任务的socket事件(比如可读、可写),当某个socket就绪时,再唤醒对应的生成器任务继续执行

4. 总结

你的调度器逻辑是正确的,但缺少了非阻塞IO和IO多路复用的关键部分,导致当前代码无法真正实现异步。不过你的研究方向完全没问题——沿着这个思路补充IO多路复用的逻辑,就能写出一个简化版的asyncio事件循环。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 17:50:29