Python 3.x 如何将基于回调的低级代码与高级异步/等待代码连接起来?

Python 3.x 如何将基于回调的低级代码与高级异步/等待代码连接起来?,python-3.x,python-asyncio,Python 3.x,Python Asyncio,文件规定: Future对象用于将基于回调的低级代码与高级异步/等待代码连接起来 有没有一个典型的例子来说明这是如何做到的 为了使示例更具体,假设我们要包装以下函数,这是一个典型的基于回调的API。需要说明的是:这个函数不能修改——假设某个复杂的第三方库(可能在内部使用我们无法控制的线程)需要回调 import threading import time def callback_after_delay(secs, callback, *args): """Call callback

文件规定:

Future对象用于将基于回调的低级代码与高级异步/等待代码连接起来

有没有一个典型的例子来说明这是如何做到的


为了使示例更具体,假设我们要包装以下函数,这是一个典型的基于回调的API。需要说明的是:这个函数不能修改——假设某个复杂的第三方库(可能在内部使用我们无法控制的线程)需要回调

import threading
import time

def callback_after_delay(secs, callback, *args):
    """Call callback(*args) after sleeping for secs seconds"""
    def _target():
        time.sleep(secs)
        callback(*args)

    thread = threading.Thread(target=_target)
    thread.start()
我们希望能够使用我们的包装函数,如:

async def main():
    await aio_callback_after_delay(10., print, "Hello, World")

下面是一个完整的自包含示例来演示一种方法。它负责在异步IO线程上运行回调,并处理回调引发的异常

适用于python 3.6.6。我想知道如何在这里使用
asyncio.get\u event\u loop()
。我们需要一个循环作为
循环。create_future()
是在asyncio中创建未来的首选方法。然而,在3.7中,我们应该更喜欢
asyncio.get\u running\u loop()
,如果尚未设置循环,这将引发异常。也许最好的方法是在延迟后显式地将循环传递到
aio\u callback\u,但这与现有的
asyncio
代码不匹配,这通常使循环成为可选的关键字参数。请澄清这一点,或作出任何其他改进,我们将不胜感激



输出类似于:

Basic Test
Before!
Hello, World
After!

Truly Async!
Before!
Before!
Before!
Before!
Before!
Hello, World
Hello, World
Hello, World
Hello, World
Hello, World
After!
After!
After!
After!
After!

Exception Handling
Before!
Traceback (most recent call last):
  File "./scratch.py", line 60, in <module>
    loop.run_until_complete(test_aio_callback_after_delay_exception())
  File "\lib\asyncio\base_events.py", line 468, in run_until_complete
    return future.result()
  File "./scratch.py", line 40, in test_aio_callback_after_delay_exception
    await aio_callback_after_delay(1., callback)
  File "./scratch.py", line 26, in aio_callback_after_delay
    return await f
  File "./scratch.py", line 21, in _inner
    f.set_result(callback(*args))
  File "./scratch.py", line 37, in callback
    raise RuntimeError()
RuntimeError
基本测试
之前
你好,世界
之后
真正的异步!
之前
之前
之前
之前
之前
你好,世界
你好,世界
你好,世界
你好,世界
你好,世界
之后
之后
之后
之后
之后
异常处理
之前
回溯(最近一次呼叫最后一次):
文件“/scratch.py”,第60行,在
循环。运行直到完成(延迟异常()后测试回调)
文件“\lib\asyncio\base\u events.py”,第468行,在运行中直到完成
返回future.result()
文件“/scratch.py”,第40行,在延迟异常之后的测试aio回调中
延迟后等待aio回调(1,回调)
文件“/scratch.py”,第26行,在aio\u回调\u延迟后
返回等待f
文件“/scratch.py”,第21行,在
f、 设置结果(回调(*args))
回调中第37行的文件“/scratch.py”
引发运行时错误()
访问违例

只需使用ThreadPoolExecutor。除了如何启动线程,代码不会改变。如果从gather调用中删除“return\u exceptions”,您将看到打印有完整回溯的异常,因此您需要什么取决于您自己

import time,random
from concurrent.futures import ThreadPoolExecutor
import asyncio

def cb():
  print("cb called")

def blocking():
  if random.randint(0,3) == 1:
    raise ValueError("Random Exception!")
  time.sleep(1)
  cb()
  return 5

async def run(loop):
  futs = []
  executor = ThreadPoolExecutor(max_workers=5)
  for x in range(5):
    future = loop.run_in_executor(executor, blocking)
    futs.append( future )

  res = await asyncio.gather( *futs, return_exceptions=True )
  for r in res:
    if isinstance(r, Exception):
      print("Exception:",r)

loop = asyncio.get_event_loop()
loop.run_until_complete( run(loop) )
loop.close()
输出

cb called
cb called
cb called
Exception: Random Exception!
Exception: Random Exception!

如果有人想知道,我想这是一个非常常见的情况,有一个简单的例子来展示确保回调在主线程上、处理回调中的异常以及如何最好地使用Future对象的最佳实践是值得的。虽然我可以发布一个工作版本,但由于我不确定它是否是“最佳实践”版本,我认为这会混淆问题。通常情况下,我们的想法是使用asyncio提供的工具来选择套接字和管道,这样就不必使用线程。这是因为线程挫败了asyncio的许多优点,例如对数千个任务的有效支持。如果线程的用例是调用旧的阻塞代码,那么您可以使用它有效地将asyncio连接到任意线程(或进程)池。我已经澄清,示例函数表示我们无法修改的第三方库。我不确定在这种情况下,
loop.run_in_executor
会有什么帮助,因为延迟后的
callback_
没有阻塞。这并没有包装现有函数-我编辑了这个问题,以明确我假设函数无法修改。
cb called
cb called
cb called
Exception: Random Exception!
Exception: Random Exception!