【问题标题】:Making file-handling code compatible with asyncio使文件处理代码与 asyncio 兼容
【发布时间】:2015-04-22 14:05:37
【问题描述】:

库接受文件输入的“传统”方式是这样的:

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 而不是使用它。

【问题讨论】:

  • 从普通文件读取不会阻塞;您立即获得文件中可用的所有字节,当它们全部消失时,调用完成。所以,没有必要yield from - 无论如何你永远不会返回控制事件循环。请参阅here 了解更多信息。
  • @dano:当然可以,但是如果客户端代码传递给我们一个异步套接字呢?
  • 如果您的 API 需要能够同时处理 asyncio 兼容对象和常规文件对象,您可能需要向 foo 添加逻辑以检测差异并做正确的事。例如。调用out = file_obj.read(),如果out 不是bytes 对象,则yield from 它。除了在线程中运行之外,没有办法让 FS 读取真正异步。
  • 需要能够处理 asyncio 对象,因为我不想给碰巧正在使用 asyncio 写东西并想要打电话到我的图书馆。
  • 你可以去aiofiles library看看。

标签: python python-3.x python-asyncio


【解决方案1】:

这是显式异步框架的缺点之一。与gevent 不同,它可以在不更改任何代码的情况下对同步代码进行monkeypatch 使其异步,你不能使同步代码asyncio 兼容而不重写它以使用asyncio.coroutineyield from(或至少asyncio.Futures和回调)一直向下。

据我所知,在asyncio 和正常的同步上下文中,相同的功能无法正常工作;任何asyncio 兼容的代码都将依赖正在运行的事件循环来驱动异步部分,因此它不会在正常上下文中工作,并且如果同步代码总是最终阻塞事件循环在asyncio 上下文中运行。这就是为什么您通常会在同步版本旁边看到asyncio 特定(或至少是异步框架特定)版本的库。没有很好的方法来呈现一个可以同时使用两者的统一 API。

【讨论】:

    【解决方案2】:

    在考虑了更多之后,我得出的结论是可以做到这一点,但它并不完全漂亮。

    从繁体版的foo()开始:

    def foo(file_obj):
        data = file_obj.read()
        # Do other things here
    

    我们需要传递一个文件对象,它会在此处表现“正确”。当文件对象需要做 I/O 时,它应该遵循这个过程:

    1. 它会创建一个新的event
    2. 它创建一个闭包,当调用该闭包时,它会执行必要的 I/O,然后设置事件。
    3. 它使用 call_soon_threadsafe() 将关闭交给事件循环。
    4. 它会阻止事件。

    下面是一些示例代码:

    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,这在某些情况下可能是可取的。

    【讨论】:

      猜你喜欢
      • 2017-07-01
      • 2023-03-15
      • 1970-01-01
      • 1970-01-01
      • 2015-10-11
      • 2019-05-12
      • 2020-11-21
      • 2014-09-03
      • 1970-01-01
      相关资源
      最近更新 更多