库采取文件输入的“传统”方式是执行以下操作:
def foo(file_obj):
data = file_obj.read()
# Do other things here
客户端代码负责打开文件,寻找适当的点(如果需要)并关闭它。如果客户想要给我们一个管道或套接字(或者StringIO
,就此而言),他们可以做到这一点并且它只是工作。
但这与asyncio不兼容,后者需要更像这样的语法:
def foo(file_obj):
data = yield from file_obj.read()
# Do other things here
当然,这种语法只适用于asyncio对象;试图将它与传统的文件对象一起使用会让人感到困惑。反之亦然。
更糟糕的是,在我看来,没有办法将此yield from
包含在传统的.read()
方法中,因为我们需要一直到事件循环,而不仅仅是在阅读发生了。 gevent库确实做了类似的事情,但是我没有看到如何使他们的greenlet代码适应生成器。
如果我正在编写处理文件输入的库,我应该如何处理这种情况?我需要两个版本的foo()
功能吗?我有很多这样的功能;复制所有这些内容是不可扩展的。
我可以告诉我的客户端开发人员使用run_in_executor()
或一些等价物,但这感觉就像对抗asyncio而不是使用它。
答案 0 :(得分:2)
这是显式异步框架的缺点之一。与gevent
不同,asyncio
可以对同步代码进行monkeypatch以使其在不进行任何代码更改的情况下进行异步,但您无法使同步代码asyncio.coroutine
- 兼容而无需重写它以使用yield from
和{{1} (或者至少asyncio.Futures
和回调)一直向下。
我不知道在asyncio
和普通的同步上下文中有相同的功能正常工作;任何asyncio
兼容的代码都将依赖于运行的事件循环来驱动异步部分,因此它不能在正常的上下文中工作,并且同步代码总是会阻塞事件循环如果它在asyncio
上下文中运行。这就是为什么您通常会看到asyncio
- 特定(或至少是异步框架)特定版本的库以及同步版本。没有什么好方法可以提供适用于这两者的统一API。
答案 1 :(得分:1)
在考虑了这个之后,我得出的结论是可以做到这一点,但它并不完美。
从传统版本的foo()
开始:
def foo(file_obj):
data = file_obj.read()
# Do other things here
我们需要传递一个在这里表现“正确”的文件对象。当文件对象需要进行I / O时,它应该遵循以下过程:
call_soon_threadsafe()
将事件循环移至事件循环。以下是一些示例代码:
import asyncio, threading
# inside the file object class
def read(self):
event = threading.Event()
def closure():
# self.reader is an asyncio StreamReader or similar
self._tmp = yield from self.reader.read()
event.set()
asyncio.get_event_loop().call_soon_threadsafe(closure)
event.wait()
return self._tmp
然后我们安排foo(file_obj)
在执行人中运行(例如,按照OP中的建议使用run_in_executor()
。)
这项技术的好处在于,即使foo()
的作者不了解asyncio
,它仍然有效。它还确保在事件循环中提供I / O,这在某些情况下是可取的。