Python 使文件处理代码与asyncio兼容
库获取文件输入的“传统”方式是执行以下操作:Python 使文件处理代码与asyncio兼容,python,python-3.x,python-asyncio,Python,Python 3.x,Python Asyncio,库获取文件输入的“传统”方式是执行以下操作: def foo(file_obj): data = file_obj.read() # Do other things here def foo(file_obj): data = yield from file_obj.read() # Do other things here 客户机代码负责打开文件、查找到适当的点(如果需要)并将其关闭。如果客户想给我们一个管道或插座(或者是一个StringIO),他们可以这样
def foo(file_obj):
data = file_obj.read()
# Do other things here
def foo(file_obj):
data = yield from file_obj.read()
# Do other things here
客户机代码负责打开文件、查找到适当的点(如果需要)并将其关闭。如果客户想给我们一个管道或插座(或者是一个StringIO
),他们可以这样做,而且可以正常工作
但这与asyncio不兼容,因为asyncio需要如下语法:
def foo(file_obj):
data = file_obj.read()
# Do other things here
def foo(file_obj):
data = yield from file_obj.read()
# Do other things here
当然,这种语法只适用于asyncio对象;试图将其用于传统的文件对象会造成混乱。反之亦然
更糟糕的是,在我看来,似乎没有办法将这个屈服从封装到传统的.read()
方法中,因为我们需要屈服到事件循环,而不仅仅是在读取发生的位置。gevent库确实做了类似的事情,但我不知道如何将它们的greenlet代码改编成生成器
如果我正在编写一个处理文件输入的库,我应该如何处理这种情况?我需要两个版本的foo()
函数吗?我有很多这样的功能;复制所有这些文件是不可伸缩的
我可以告诉我的客户机开发人员使用或类似的工具,但这感觉像是针对asyncio而不是使用它。这是显式异步框架的缺点之一。与gevent
不同,它可以在不更改任何代码的情况下对同步代码进行monkeypatch synchronous code,使其异步,而不将同步代码asyncio
重写为使用asyncio.coroutine
和始终从中获益(或至少asyncio.Futures
和回调)
据我所知,在asyncio
和正常的同步上下文中,无法让相同的函数正常工作;任何与asyncio
兼容的代码都将依赖于要运行的事件循环来驱动异步部分,因此它在正常上下文中无法工作,而同步代码如果在asyncio
上下文中运行,则总是会阻塞事件循环。这就是为什么在同步版本旁边通常会看到特定于库的asyncio
(或至少特定于异步框架)版本。没有一种好的方法可以提供一个同时适用于两者的统一API。经过进一步考虑,我得出结论,这是可能的,但它并不完美
从传统版本的foo()
开始:
我们需要传递一个文件对象,它在这里的行为“正确”。当文件对象需要进行I/O时,应遵循以下过程:
它创造了一个新的世界
它创建一个闭包,当调用该闭包时,执行必要的I/O,然后设置事件
它使用将闭包交给事件循环
它阻碍了这项活动
下面是一些示例代码:
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,这在某些情况下可能是可取的。从普通文件读取不会阻塞;您可以立即获得文件中所有可用的字节,当它们全部消失时,调用完成。因此,无需从
中屈服-无论如何,您永远不会返回控制事件循环。有关详细信息,请参阅。@dano:当然,但是如果客户端代码向我们传递了一个异步IO套接字,该怎么办?如果您的API需要能够处理asyncio
兼容对象和常规文件对象,您可能需要向foo
添加逻辑,以检测差异并做正确的事情。例如,调用out=file_obj.read()
,如果out
不是bytes
对象,则从它中产生。除了在线程中运行,没有办法使FS真正异步读取。我不需要处理asyncio对象,因为我不想给那些碰巧使用asyncio编写东西并希望调用我的库的人带来不便。你可以看看。