file.writealltextasync最简单但需注意编码和路径合法性,默认utf-8无bom;streamwriter配合using适合流式写入;并发写文件需加锁或用日志框架保障线程安全。

用 File.WriteAllTextAsync 最简单,但要注意编码和路径合法性
直接替换同步的 File.WriteAllText 即可,它返回 Task,支持 await。默认使用 UTF-8(无 BOM),如果需要带 BOM 或其他编码(如 GB2312),必须显式传入 Encoding 参数,否则可能乱码:
await File.WriteAllTextAsync("log.txt", "内容", Encoding.UTF8);
常见错误是传入 null 路径或包含非法字符(如 *、?、)的文件名,会抛出 <code>ArgumentException;目录不存在时不会自动创建,需提前调用 Directory.CreateDirectory。
StreamWriter 配合 using 是流式写入的推荐方式
适合写入大文本、分段追加、或需要控制缓冲行为的场景。必须用 async 版本的 WriteAsync 和 WriteLineAsync,否则会阻塞线程:
using var sw = new StreamWriter("data.json", append: true) { AutoFlush = true };
await sw.WriteLineAsync(JsonSerializer.Serialize(obj));
注意点:
-
AutoFlush = true可避免 await 期间数据滞留在缓冲区未落盘 - 不要手动调用
sw.Flush()—— 它是同步的,会抵消 async 效果 - 若需指定编码,构造时传入
new StreamWriter(path, append, encoding)
别用 FileStream.WriteAsync 直接写字符串,容易出编码问题
它只接受 byte[],必须自己编码转换。新手常犯的错是这样写:
// ❌ 错误:没指定编码,且 string → byte[] 转换不明确 var bytes = Encoding.Default.GetBytes(text); await fs.WriteAsync(bytes, 0, bytes.Length);
正确做法是统一用 UTF8 并显式处理:
var utf8Bytes = Encoding.UTF8.GetBytes(text); await fs.WriteAsync(utf8Bytes, 0, utf8Bytes.Length);
但除非你有特殊性能要求(比如复用缓冲区、零分配写入),否则优先走 StreamWriter 或 File.WriteAllTextAsync,更安全。
并发写同一个文件?小心 IOException 和数据覆盖
File.WriteAllTextAsync 是“覆盖写”,多任务同时调用会导致后写入者完全覆盖前者的全部内容;StreamWriter 开启 append: true 也只是在打开时定位到末尾,写入过程仍非原子操作。真实并发场景下:
- 用
lock或SemaphoreSlim控制写入顺序 - 改用日志框架(如 Serilog)内置的异步文件 sink,它已处理队列与线程安全
- 或把每条记录写入独立临时文件,再由单个后台任务合并
异步不等于线程安全 —— 这是最容易被忽略的前提。










