这两个类代表了并发编程的优秀抽象,因此他们不支持相同的API有点令人不安。
具体来说,根据docs:
asyncio.Future
几乎与concurrent.futures.Future
兼容。的差异:
result()
和exception()
不会采用超时参数,并在未来尚未完成时引发异常。- 始终通过事件循环
add_done_callback()
调用call_soon_threadsafe()
注册的回调。- 此类与
wait()
包中的as_completed()
和concurrent.futures
函数不兼容。
以上列表实际上是不完整的,还有一些差异:
- 如果过早打电话,
running()
方法不存在result()
和exception()
可能会提升InvalidStateError
这些是否由于事件循环的固有特性导致这些操作无法实施或太麻烦?
与add_done_callback()
相关的区别是什么意思?无论哪种方式,回调都保证在期货完成后的某个未指定的时间发生,所以这两个类之间是否完全一致?
答案 0 :(得分:6)
区别的核心原因在于线程(和进程)如何处理块与协程如何处理阻塞的事件。在线程中,当前线程被挂起,直到任何条件结束并且线程可以继续。例如,在期货的情况下,如果您请求未来的结果,暂停当前线程直到该结果可用为止是没有问题的。
然而,事件循环的并发模型不是暂停代码,而是返回事件循环并在准备好后再次调用。因此,请求未完成结果的asyncio未来结果是错误的。
你可能会认为asyncio的未来可能只是等待,虽然这样效率很低,你的协程会阻止它真的那么糟糕吗?事实证明,拥有协程块很可能意味着未来永远不会完成。很可能未来的结果将由与运行请求结果的代码的事件循环相关联的一些代码设置。如果运行该事件循环的线程阻塞,则不会运行与事件循环关联的代码。因此阻塞结果会导致死锁并阻止生成结果。
所以,是的,界面的差异是由于这种固有的差异。举个例子,你不想在concurrent.futures服务器抽象中使用asyncio future,因为这会阻塞事件循环线程。
add_done_callbacks
差异保证了回调将在事件循环中运行。这是可取的,因为他们将获得事件循环的线程本地数据。此外,许多协程代码假定它永远不会与来自同一事件循环的其他代码同时运行。也就是说,在假设来自同一事件循环的两个协同程序不同时运行的情况下,协程只是线程安全的。在事件循环中运行回调可以避免很多线程安全问题,并且可以更容易地编写正确的代码。
答案 1 :(得分:5)
concurrent.futures.Future
提供了一种在使用Executor时通常在不同线程和进程之间共享结果的方法。
asyncio.Future
解决了相同的任务,但对于coroutines,实际上是一些特殊类型的函数,通常在一个进程/线程中异步运行。 "异步"在当前上下文中意味着事件循环管理执行此协程流程的代码:它可以暂停在一个协程内执行,开始执行另一个协同程序,然后返回执行第一个协程 - 通常在一个线程/进程中执行。
这些对象(以及Lock
,Event
,Semaphore
等许多其他threading / asyncio对象看起来很相似,因为并发的概念你的代码与线程/进程和协程类似。
我认为对象不同的主要原因是历史:asyncio
的创建时间晚于threading
和concurrent.futures
。在不破坏类API的情况下,可能无法更改concurrent.futures.Future
以使用asyncio
。
两个班级是否应该成为理想世界中的一个"?这可能是值得商榷的问题,但我看到了许多不利之处:虽然asyncio
和threading
看起来很相似,但它们在很多方面都有很大不同,包括内部实施或写作方式asyncio / non-asyncio代码(请参阅async
/ await
个关键字)。
我认为它可能是最好的,因为类不同:我们明显地将不同的并发方式分开(即使它们的相似性最初看起来很奇怪)。