非阻塞文件读取

15

如何以非阻塞模式读取二进制或文本文件的内容?

对于二进制文件:当我使用 open(filename, mode='rb') 后,会得到一个 io.BufferedReader 实例。在 io.BufferedReader.read 的文档中写道:

读取并返回 size 字节,如果未指定 size 或为负数,则读取直到 EOF 或者以非阻塞模式调用读取操作。

显然,简单的 open(filename, 'rb').read() 是阻塞模式。令我惊讶的是,在 io 的文档中找不到任何说明如何选择非阻塞模式的解释。

对于文本文件:当我使用 open(filename, mode='rt') 后,会得到一个 io.TextIOWrapper。我假设相关文档是其基类 io.TextIOBase 中的 read,根据该文档,似乎根本没有办法进行非阻塞读取:

从流中最多读取 size 个字符作为单个 str。如果 size 为负或为 None,则读取直到 EOF。


不确定您是否需要使用os.O_NONBLOCK在较低级别进行此操作。 - Padraic Cunningham
请告诉我为什么您要给我的回答点踩?是因为它不相关吗? - Juggernaut
@Amin Etesamian,我没有点踩,也不确定为什么其他读者不喜欢它。尽管我不知道aiofiles库,但它似乎很相关。 - max
@max 我在一个异步文件服务中使用了它。它运行得非常好。 - Juggernaut
3个回答

12

文件操作是阻塞的,没有非阻塞模式。

但您可以创建一个线程在后台读取文件。在Python 3中,concurrent.futures 模块可能会有所帮助。

from concurrent.futures import ThreadPoolExecutor

def read_file(filename):
    with open(filename, 'rb') as f:
        return f.read()

executor = concurrent.futures.ThreadPoolExecutor(1)
future_file = executor.submit(read_file, 'C:\\Temp\\mocky.py')

# continue with other work

# later:

if future_file.done():
    file_contents = future_file.result()

或者,如果您需要在操作完成时调用回调函数:

def on_file_reading_finished(future_file):
    print(future_file.result())

future_file = executor.submit(read_file, 'C:\\Temp\\mocky.py')
future_file.add_done_callback(on_file_reading_finished)

# continue with other code while the file is loading...

5
文档中所提到的非阻塞模式仅适用于非文件流?难道文件读取不能是非阻塞的吗?请问是否有原因导致这样做? - max
这个解决方案似乎没有提供任何中断阻塞读取以关闭文件的方法,就像非阻塞访问那样容易。这可能可以通过向进程发送信号来实现,或者也许有其他取消线程的方法。我猜测Python文件对象被设计为处理非阻塞底层文件描述符,这可以通过os模块获取。 - fuzzyTew

11

Python支持非阻塞读取,在Unix类型系统上,可以通过设置O_NONBLOCK标志位来实现。在Python 3.5+中,有os.set_blocking()函数使其更加容易:

import os
f = open(filename, 'rb')
os.set_blocking(f.fileno(), False)
f.read()  # This will be non-blocking.

然而,正如zvone's answer所指出的那样,这并不一定适用于实际的磁盘文件。这不是Python的问题,而是操作系统的限制。正如Linux open(2) man page所述:
注意,此标志对普通文件和块设备没有影响;也就是说,无论是否设置O_NONBLOCK,I/O操作都将(简要)阻塞,当需要设备活动时。
但它确实建议将来可能会实现这个功能:
由于O_NONBLOCK语义可能最终会被实现,因此应用程序在为普通文件和块设备指定此标志时不应依赖阻塞行为。

2
如果您的 Python 版本低于 3.5,无法使用 os.set_blocking,请尝试使用 fcntl.fcntl(f.fileno(), fcntl.F_SETFL, flag | os.O_NONBLOCK) - BeardOverflow
open() 函数必须指定“读取二进制”选项(rb)吗?我尝试了不带二进制选项的方式,似乎也可以工作。 - Ben Slade
普通文件始终可读,因此设置该位不起作用,更多信息请参阅此帖子:https://www.remlab.net/op/nonblock.shtml - undefined

6

我建议使用aiofiles - 这是一个专门为在asyncio应用程序中处理本地磁盘文件而设计的库。

import aiofiles

async def read_without_blocking():
    f = await aiofiles.open('filename', mode='r')
    try:
        contents = await f.read()
    finally:
        await f.close()

3
您的收益声明应该放在什么样的情境下并不是非常清晰,您能否提供一个更具实际意义的例子呢? - Tadhg McDonald-Jensen
我该如何调用异步函数?我收到一个错误,说这个函数没有被等待。我该如何从同步函数中调用异步函数? - Shailesh
@Yankee https://dev59.com/T1MI5IYBdhLWcg3wptVR - Juggernaut
@Juggernaut,很抱歉,这仍然不清楚如何使用上面的代码。您能否编辑您的代码以包含一个示例执行? - Shailesh
这可能是这里最好的答案。遇到的任何怪癖都可以抽象成一个维护的库。缺点是asyncio涉及与正常同步代码不同的Python编码范例。需要阅读Python asyncio模块的介绍,启动运行循环并编写异步函数以使用该库。这个答案可能需要更新现代代码,我不确定'yield from'是否仍然是asyncio的一部分,可能是错误的。 - fuzzyTew

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接