我对如何使用Python 3.4中的asyncio
模块感到困惑。我有一个用于搜索引擎的searching
API,并希望每个搜索请求可以并行或异步运行,这样我就不必等待一次搜索完成另一次搜索。
这是我的高级搜索API,用于使用原始搜索结果构建一些对象。搜索引擎本身正在使用某种asyncio机制,所以我不会为此烦恼。
# No asyncio module used here now
class search(object):
...
self.s = some_search_engine()
...
def searching(self, *args, **kwargs):
ret = {}
# do some raw searching according to args and kwargs and build the wrapped results
...
return ret
为了尝试异步请求,我编写了以下测试用例来测试我如何与asyncio
模块交互我的东西。
# Here is my testing script
@asyncio.coroutine
def handle(f, *args, **kwargs):
r = yield from f(*args, **kwargs)
return r
s = search()
loop = asyncio.get_event_loop()
loop.run_until_complete(handle(s.searching, arg1, arg2, ...))
loop.close()
通过使用pytest运行,当它到达RuntimeError: Task got bad yield : {results from searching...}
行时,它将返回r = yield from ...
。
我也尝试过另一种方式。
# same handle as above
def handle(..):
....
s = search()
loop = asyncio.get_event_loop()
tasks = [
asyncio.async(handle(s.searching, arg11, arg12, ...)),
asyncio.async(handle(s.searching, arg21, arg22, ...)),
...
]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
通过pytest运行此测试用例,它会通过,但搜索引擎会产生一些奇怪的异常。它说Future/Task exception was never retrieved
。
我想问的事情:
yield from
的正确方法吗?search
是否需要包含loop = get_event_loop()
这类内容来同步请求?答案 0 :(得分:17)
问题在于,您不能像调用asyncio.coroutine
一样调用现有的同步代码并获得异步行为。当您致电yield from searching(...)
时,如果searching
本身实际上是asyncio.coroutine
,或者至少返回asyncio.Future
,则您只会获得异步行为。现在,searching
只是一个常规的同步函数,因此调用yield from searching(...)
只会引发错误,因为它不会返回Future
或协程。
要获得您想要的行为,除了searching
版本之外,您还需要拥有synchronous
的异步版本(如果您没有&#39,则完全放弃同步版本) ;需要它)。您可以选择支持两者:
searching
重写为asyncio.coroutine
,使用asyncio
- 兼容调用来执行其I / O,而不是阻止I / O.这将使其在asyncio
上下文中工作,但这意味着您无法再在同步上下文中直接调用它。相反,您还需要提供另一种同步searching
方法,以启动asyncio
事件循环并调用return loop.run_until_complete(self.searching(...))
。有关详细信息,请参阅this question。保持searching
的同步实现,并提供使用BaseEventLoop.run_in_executor
在后台线程中运行searching
方法的备用异步API:
class search(object):
...
self.s = some_search_engine()
...
def searching(self, *args, **kwargs):
ret = {}
...
return ret
@asyncio.coroutine
def searching_async(self, *args, **kwargs):
loop = kwargs.get('loop', asyncio.get_event_loop())
try:
del kwargs['loop'] # assuming searching doesn't take loop as an arg
except KeyError:
pass
r = yield from loop.run_in_executor(None, self.searching, *args) # Passing None tells asyncio to use the default ThreadPoolExecutor
return r
测试脚本:
s = search()
loop = asyncio.get_event_loop()
loop.run_until_complete(s.searching_async(arg1, arg2, ...))
loop.close()
这样,您可以按原样保留同步代码,并且至少提供可以在asyncio
代码中使用的方法,而不会阻止事件循环。它并不像你在代码中实际使用异步I / O那样干净,但它总比没有好。
searching
版本,一个使用阻止I / O,另一个使用asyncio
兼容。这为两个上下文提供了理想的实现,但需要两倍的工作。