用 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,它已处理队列与线程安全 或把每条记录写入独立临时文件,再由单个后台任务合并
异步不等于线程安全 —— 这是最容易被忽略的前提。
