为什么asyncio.Future与concurrent.futures.Future不兼容?

12
这两个类都是并发编程的优秀抽象,但有点令人不安的是它们不支持相同的API。
具体来说,根据docs:
asyncio.Future几乎与concurrent.futures.Future兼容。
差异:
- result()和exception()不接受超时参数,并在未完成future时引发异常。 - 使用add_done_callback()注册的回调始终通过事件循环的call_soon_threadsafe()调用。 - 该类与concurrent.futures包中的wait()和as_completed()函数不兼容。
上面的列表实际上是不完整的,还有一些差异。
  • running() 方法不存在
  • result()exception() 如果过早调用可能会引发 InvalidStateError

这些问题是否由事件循环的固有特性造成,使得这些操作要么无用,要么太麻烦而不值得实现?

关于 add_done_callback() 的差异是什么意思?无论哪种方式,回调都保证在未来某个不确定的时间发生,因此在这两个类之间完全一致,是吗?

2个回答

8
线程(和进程)处理阻塞的方式不同于协程处理阻塞事件,这是两者差异的核心原因。在线程中,当前线程会被挂起,直到某个条件解决并线程可以继续执行。例如,在使用 futures 时,如果您请求 future 的结果,暂停当前线程直到结果可用是没问题的。
但是,事件循环的并发模型是,与其挂起代码,不如返回到事件循环并在准备好时再次调用。因此,请求尚未就绪的 asyncio future 的结果是错误的。
也许你认为 asyncio future 可以等待,虽然这会很低效,但协程阻塞会有多糟糕呢?但事实证明,使协程阻塞非常可能意味着未来永远不会完成。未来的结果很可能由与运行请求结果代码相关联的事件循环运行的代码设置。如果运行该事件循环的线程被阻塞,则不会运行与事件循环相关的任何代码。因此,在结果上阻塞会导致死锁,并阻止结果的生成。
因此,是的,接口差异是由这种固有差异引起的。例如,您不想使用 asyncio future 与 concurrent.futures 等待器抽象,因为这将再次阻塞事件循环线程。
"add_done_callback" 的差异保证回调将在事件循环中运行。这是可取的,因为它们将获得事件循环的线程局部数据。此外,许多协程代码假定它永远不会与同一事件循环中的其他代码同时运行。也就是说,协程仅在假设来自同一事件循环的两个协程不同时运行的情况下是线程安全的。在事件循环中运行回调避免了许多线程安全问题,并使编写正确的代码更加容易。

7
concurrent.futures.Future提供了一种在不同线程和进程之间共享结果的方式,通常在使用Executor时使用。 asyncio.Future解决了相同的任务,但针对协程,它们实际上是一些特殊类型的函数,通常在一个进程/线程中异步运行。在当前上下文中,“异步”意味着事件循环管理此协程的代码执行流程:它可以暂停执行一个协程,在开始执行另一个协程后返回到执行第一个协程-通常在一个线程/进程中完成。
这些对象(以及许多其他线程/异步对象,如LockEventSemaphore等)看起来很相似,因为使用线程/进程和协程实现并发的思想是相似的。
我认为对象之所以不同是历史原因:asynciothreadingconcurrent.futures创建得更晚。可能无法更改concurrent.futures.Future以使用asyncio而不破坏类API。
在“理想世界”中,这两个类应该是一个吗?这可能是有争议的问题,但我认为它们有很多缺点:虽然asynciothreading乍一看很相似,但它们在许多方面非常不同,包括内部实现或编写异步/非异步代码的方式(请参见async/await关键字)。
我认为最好还是将这些类分开:我们清楚地分离了不同性质的并发方式(即使它们的相似之处乍一看很奇怪)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接