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编写东西并希望调用我的库的人带来不便。你可以看看。