使文件处理代码与asyncio

时间:2015-04-22 14:05:38

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

库采取文件输入的“传统”方式是执行以下操作:

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

2 个答案:

答案 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时,它应该遵循以下过程:

  1. 它会创建一个新的event
  2. 它创建一个闭包,当被调用时,它执行必要的I / O然后设置事件。
  3. 使用call_soon_threadsafe()将事件循环移至事件循环。
  4. 它阻止了这一事件。
  5. 以下是一些示例代码:

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