Skip to main content
 首页 » 编程设计

python之在 Python 中使用 asyncio 并行化 Web 任务

2024年08月26日48rubylouvre

我正试图全神贯注于 asyncio 和 aiohttp,多年来编程让我第一次感到自己非常愚蠢和无能。这有点漂亮,以一种古怪的禅宗方式。但是,唉,还有工作要做。

我有一个现有的类可以在网络上做许多奇妙的事情,比如注册网站、获取数据等。现在我需要 100 或 1000 只这样的小工蜂来注册。代码大致如下所示:

class Worker(object): 
    def signup(self, ...): 
        ... 
        data = self.make_request(url, data) 
        self.user_id = data.get("user_id") 
        return self 
 
    def make_request(self, url, data): 
        response = requests.post(url, data=data) 
        return response.json() 
 
workers = [Worker().signup() for n in range(100)] 

如您所见,我们正在使用请求模块发出 POST 请求。然而,这是阻塞的,所以我们必须等待 worker N 完成注册,然后才能开始注册 worker N+1。幸运的是,Worker 类的原作者(这听起来很有马克思主义色彩)以她无限的智慧将每个 HTTP 调用都包装在 self.make_request 方法中,所以让整个 Worker 非阻塞应该只是一个问题将请求库换成一个非阻塞的 aaaa 和 bob 是你的叔叔,对吧?这是我得到的结果:

class AyncWorker(Worker): 
    @asyncio.coroutine 
    def make_request(self, url, data): 
        response = yield from aiohttp.request('post', url, data=data) 
        return (yield from response.json()) 
 
coroutines = [Worker().signup() for n in range(100)] 
loop = asyncio.get_event_loop() 
loop.run_until_complete(asyncio.wait(coroutines)) 
loop.close() 

但是这会在 signup 方法中引发一个 AttributeError: 'generator' object has no attribute 'get' 我做 self.user_id = data.获取(“用户 ID”)。除此之外,我仍然没有一本整洁的字典中的 worker 。我知道我很可能完全误解了 asyncio 的工作原理——但我已经花了一天时间阅读各种文档、David Beazly 令人心碎的教程,以及大量足以让我理解它们的玩具示例简单适用于这种情况。我应该如何构建我的工作人员和我的异步循环以并行注册 100 个工作人员并最终在他们注册后获得所有工作人员的列表?

请您参考如下方法:

一旦你在一个函数中使用了yield(或yield from),这个函数就变成了协程。这意味着您无法仅通过调用它来获得结果:您将获得一个generator object。您至少必须这样做:

@asyncio.coroutine 
def some_coroutine(*args): 
    #... 
    #... 
    yield from tasty.asyncio.function() 
    return result 
 
def coroutine_user(): 
    # data = some_coroutine() will give you a generator object instead of result 
    data = yield from some_coroutine() 
    return data # data here is a plain result: you can call your .get or whatever 

猜猜调用 coroutine_user() 时会发生什么:

>>> coroutine_user() 
<generator object coroutine_user at 0x7fe13b8a47e0> 

缺少 async.coroutine 装饰器根本无济于事:协程具有传染性!要在函数中获取结果,您必须使用 yield from。它将您的函数变成另一个协程!

虽然事情并不总是那么糟糕(通常你可以手动迭代生成器对象而不依赖于 yield from),asyncio 会特别阻止你这样做:它破坏了一些内部结构(你可以仅从 Future 或 asyncio.coroutine 执行)。所以只要使用 concurrent.futures 或类似的东西,除非你要把你所有的代码都变成协程。作为替代方案,将 aiohttp.request 的所有用户与常用方法隔离开来,并使用基于协程的异步 worker 和同步纯旧代码。深入研究 asyncio 并实际重构所有代码也是一种选择,显然:您基本上需要在每次调用任何受感染的 asyncio 方法之前放置 yield from。