Python异步IO任务的产量很低

Python异步IO任务的产量很低,python,asynchronous,python-asyncio,Python,Asynchronous,Python Asyncio,我对如何使用Python 3.4中的asyncio模块感到困惑。我有一个搜索引擎的searchAPI,希望每个搜索请求要么并行运行,要么异步运行,这样我就不必等待一个搜索完成再开始另一个搜索 下面是我的高级搜索API,用于使用原始搜索结果构建一些对象。搜索引擎本身正在使用某种异步机制,所以我不想为此费心 # No asyncio module used here now class search(object): ... self.s = some_search_engine() .

我对如何使用Python 3.4中的
asyncio
模块感到困惑。我有一个搜索引擎的
search
API,希望每个搜索请求要么并行运行,要么异步运行,这样我就不必等待一个搜索完成再开始另一个搜索

下面是我的高级搜索API,用于使用原始搜索结果构建一些对象。搜索引擎本身正在使用某种异步机制,所以我不想为此费心

# 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运行,当它到达
r=yield from…
行时,它将返回
RuntimeError:Task got bad yield:{results from search…}

我还尝试了另一种方法

# 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运行这个测试用例,它通过了,但是搜索引擎会出现一些奇怪的异常。它还说未来/任务异常从未被检索到

我想问的是:

  • 在我的第一次尝试中,通过返回函数调用的实际结果,这是使用
    生成的正确方法吗
  • 我想我需要在我的第二个测试用例中添加一些睡眠来等待任务完成,但是我应该怎么做呢?在第二个测试用例中,我如何让函数调用返回
  • 通过创建异步处理程序来处理请求,这是使用现有模块实现asyncio的好方法吗
  • 如果问题2的答案是“否”,是否每个调用类
    搜索
    的客户端都需要包含
    loop=get\u event\u loop()
    这种东西来异步请求

  • 问题是,您不能像调用
    asyncio.coroutine
    那样调用现有的同步代码并获取异步行为。当您调用
    yield from search(…)
    时,如果
    search
    本身实际上是一个
    asyncio.coroutine
    ,或者至少返回一个
    asyncio.Future
    ,那么您只会得到异步行为。现在,
    search
    只是一个常规的同步函数,因此调用
    yield from search(…)
    只会抛出一个错误,因为它不会返回
    Future
    或corroutine

    要获得所需的行为,除了同步版本之外,您还需要一个异步版本的
    搜索
    (或者如果不需要同步版本,就完全删除同步版本)。您有几个选项可以同时支持这两种功能:

  • 搜索
    重写为一个
    asyncio.coroutine
    ,它使用
    asyncio
    兼容的调用来执行其I/O,而不是阻塞I/O。这将使它在
    asyncio
    上下文中工作,但这意味着您将无法在同步上下文中直接调用它。相反,您还需要提供一个替代的同步
    搜索
    方法,该方法启动
    asyncio
    事件循环并调用
    返回循环。运行直到完成(self.search(…)
    )。有关这方面的更多详细信息,请参阅
  • 保持
    搜索
    的同步实现,并提供一个替代的异步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,那么它就不是一个干净的解决方案,但总比什么都没有好

  • 提供两个完全独立的
    搜索版本
    ,一个使用阻塞I/O,另一个兼容
    asyncio
    。这为这两种上下文提供了理想的实现,但需要两倍的工作量

  • 杰出的非常感谢你!我看到了一些关于AcsiCIO的C++代码,它们使用了<代码>无效的FO(..){Bin(FuixCalin,Y1,E2,…);},并在<代码> Vo.Poovi-Calin(..)< /Calp>中执行实际的实现。这与您的第二个选项非常相似吗?@ourlord是的,如果您所说的
    foo
    函数在后台线程中调用
    foo\u callin
    (听起来可能就是这样,这取决于所称的
    bind(foo\u callin,…)
    ),它所做的事情与第二个选项非常相似。哦,是的,我记得
    bind
    函数位于一个自实现
    async
    命名空间下,该命名空间应该包括一个线程/任务调度器或管理器。事情现在变得非常清楚了!再次感谢您的评论!