c# 在 C# 中使用 FileStream 的异步 API 和性能

来源:这里教程网 时间:2026-02-21 17:37:34 作者:

FileStream 异步 API 为什么默认不加速?

直接调用

FileStream.ReadAsync
FileStream.WriteAsync
在多数情况下并不会比同步版本快,甚至更慢——除非你显式启用了操作系统级别的异步 I/O 支持。.NET 默认创建的
FileStream
实例使用的是「同步句柄 + 线程池模拟异步」模式,本质是把
Read
/
Write
丢进
ThreadPool
执行,并非真正的 overlapped I/O。

关键判断点:只有在构造时传入

useAsync: true
,且底层文件系统支持(如 NTFS、ReFS,且非 FAT32/网络共享/某些 Docker 卷),才能触发真正的异步 I/O。

new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096, useAsync: true)
—— 必须显式设为
true
若省略
useAsync
或设为
false
,即使调用
ReadAsync
,也走线程池模拟
Windows 上启用
useAsync: true
后,.NET 会调用
CreateFile
FILE_FLAG_OVERLAPPED
标志打开句柄

useAsync = true 的实际限制和常见失败场景

设了

useAsync: true
不代表一定成功。运行时可能静默降级回同步模拟,尤其在以下情况:

打开的是重定向的 stdin/stdout(如控制台应用中
Console.OpenStandardInput()
路径指向 FAT32 分区、SMB 共享(Windows)、CIFS/NFS 挂载点(Linux/macOS) 文件被其他进程以不兼容方式打开(例如未设
FILE_SHARE_READ
Docker 容器中挂载的 host 目录(取决于存储驱动和宿主机 FS)

验证是否真异步:检查

FileStream.IsAsync
属性——它只反映构造时是否请求了异步,不保证 OS 层面生效;更可靠的方式是用 ETW 或 PerfView 观察
ThreadPoolWorkerThread
是否被大量占用,或用 Process Explorer 查看句柄属性中的 «Overlapped» 标志。

缓冲区大小与 async 性能的关系

异步 I/O 的吞吐优势高度依赖缓冲区大小。太小(如 256 字节)会导致频繁的系统调用和完成端口调度开销;太大(如 1 MB)可能浪费内存且不提升速度,尤其对 SSD 或高速 NVMe。

推荐值范围:

普通 HDD / SATA SSD:
bufferSize = 64 * 1024
(64 KB)
NVMe / 高并发服务:
bufferSize = 128 * 1024
256 * 1024
避免
bufferSize
小于 4 KB(NTFS 最小簇大小),否则底层可能拆成多个 IRP

注意:

bufferSize
FileStream
内部缓冲,和
Stream.ReadAsync
传入的
Memory<byte></byte>
大小无关——后者只是用户目标缓冲,前者才影响系统调用效率。

真实高性能异步读写的典型写法

下面是一个兼顾正确性、可诊断性和性能的

FileStream
异步读取示例,包含错误防护和关键注释:

var options = new FileStreamOptions
{
    Access = FileAccess.Read,
    Mode = FileMode.Open,
    Share = FileShare.Read,
    BufferSize = 65536,
    Options = FileOptions.Asynchronous // 等价于 useAsync: true,更明确
};
<p>await using var fs = new FileStream(@"C:\data.bin", options);</p><p>// 使用栈分配 Span 避免 GC 压力(仅限固定大小场景)
var buffer = new byte[65536];
int totalRead = 0;</p><p>while (true)
{
int read = await fs.ReadAsync(buffer, CancellationToken.None);
if (read == 0) break;
totalRead += read;
// 处理 buffer[..read],例如写入网络或解析
}

关键点:用

FileStreamOptions
替代老式构造函数,语义清晰;
FileOptions.Asynchronous
是唯一可靠的开启方式;循环中不要重复 new byte[],复用缓冲区。

真正难处理的不是 API 调用,而是确认「异步是否落地」——这需要结合 OS 层观测,而不是只看代码有没有

async
关键字。

相关推荐