Python AsyncIO的loop.add_reader()期望哪个文件描述符对象?

9 投票
2 回答
9684 浏览
提问于 2025-04-18 17:35

我正在尝试理解如何在Python 3.4中使用新的AsyncIO功能,但在使用event_loop.add_reader()时遇到了困难。从我找到的有限讨论来看,这个功能似乎是用来读取一个单独进程的标准输出,而不是打开文件的内容。这个说法对吗?如果是这样的话,似乎没有AsyncIO特定的方法来处理标准文件输入输出,这也是对的吗?

我在玩以下代码。运行这段代码时,出现了异常PermissionError: [Errno 1] Operation not permitted,这个错误来自/python3.4/selectors.py的第399行self._epoll.register(key.fd, epoll_events),是由下面的add_reader()这一行引发的。

import asyncio
import urllib.parse
import sys
import pdb
import os

def fileCallback(*args):
    pdb.set_trace()

path = sys.argv[1]
loop = asyncio.get_event_loop()
#fd = os.open(path, os.O_RDONLY)
fd = open(path, 'r')
#data = fd.read()
#print(data)
#fd.close()
pdb.set_trace()
task = loop.add_reader(fd, fileCallback, fd)
loop.run_until_complete(task)
loop.close()

编辑

对于那些想要了解如何使用AsyncIO同时读取多个文件的人,这里有一个示例,展示了如何实现这一点。关键在于这一行yield from asyncio.sleep(0)。这实际上是暂停当前的函数,把它放回事件循环的队列中,以便在其他所有准备好的函数执行完后再调用。函数是否准备好是根据它们的调度方式来决定的。

import asyncio

@asyncio.coroutine
def read_section(file, length):
    yield from asyncio.sleep(0)
    return file.read(length)

@asyncio.coroutine
def read_file(path):
    fd = open(path, 'r')
    retVal = []
    cnt = 0
    while True:
        cnt = cnt + 1
        data = yield from read_section(fd, 102400)
        print(path + ': ' + str(cnt) + ' - ' + str(len(data)))
        if len(data) == 0:
            break;
    fd.close()

paths = ["loadme.txt", "loadme also.txt"]
loop = asyncio.get_event_loop()
tasks = []
for path in paths:
    tasks.append(asyncio.async(read_file(path)))
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

2 个回答

0

你不能在本地文件上使用add_reader,原因有以下几点:

  • 因为这不能通过select/poll/epoll来实现
  • 这还跟操作系统有关
  • 由于操作系统的限制,它不能完全异步(比如Linux不支持异步读取/写入文件的元数据)

不过,从技术上讲,你确实应该能够进行异步的文件读写,几乎所有系统都有DMA机制,可以在“后台”进行输入输出操作。而且,本地输入输出并没有那么快,实际上CPU的速度是磁盘输入输出的几百万倍。

如果你想尝试异步输入输出,可以看看aiofile或aiofiles。

10

这些函数需要的是文件描述符,也就是操作系统使用的底层整数,而不是Python的文件对象。基于文件描述符的文件对象可以通过fileno()方法返回这个描述符,比如:

>>> sys.stderr.fileno()
2

在Unix系统中,文件描述符可以关联到文件,或者其他很多东西,包括其他进程。

编辑:根据提问者的更新:

正如评论中的Max所说,你不能在本地文件上使用epoll(而asyncio使用了epoll)。是的,这听起来有点奇怪。不过,你可以在管道上使用它,比如:

import asyncio
import urllib.parse
import sys
import pdb
import os

def fileCallback(*args):
    print("Received: " + sys.stdin.readline())

loop = asyncio.get_event_loop()
task = loop.add_reader(sys.stdin.fileno(), fileCallback)
loop.run_forever()

这会回显你在标准输入(stdin)上写的内容。

撰写回答