You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用StreamWriter.WriteLineAsync结合WhenAll是否存在问题?

问题描述

我有一段基于.NET Standard 2.1的异步写入MemoryStream的代码,逻辑如下:

  • 为每一行创建一个热任务;
  • 等待所有任务执行完成;
  • 最后将writer缓冲区刷新到MemoryStream。

代码示例:

await using var memoryStream = new MemoryStream();
await using var writer = new StreamWriter(memoryStream);

var recordTasks = stringRecordsToWrite.Select(r => writer.WriteLineAsync(r));

await Task.WhenAll(recordTasks);

await writer.FlushAsync();

var result = memoryStream.ToArray();

我有两个疑问:

  1. 有反馈称偶尔会出现记录丢失的情况,该实现是否是问题根源?我本地尝试复现但未成功;
  2. Resharper提示SELECT语句中的writer是被捕获的变量且在外层作用域释放,这是否会引发问题?

或是这些都是误判,问题出在其他地方?

附尝试复现问题的代码(生成约250MB文件仍未复现问题):

internal class Program
{
    public static async Task Main(string[] args)
    {
        var records = new Dictionary<string, IEnumerable<RecordsToWrite>>();

        for (var i = 0; i < 200; i++)
        {
            var recordKey = $"test-{i}";

            records.Add(recordKey, default);

            var itemRecords = new List<RecordsToWrite>();

            for (var x = 0; x < 500; x++)
            {
                itemRecords.Add(new RecordsToWrite
                {
                    Tracking = $"{recordKey}-Track-Ref-{x}"
                });
            }

            records[recordKey] = itemRecords;
        }


        var resultAsBytes = new List<byte>();
        var randomizer = new Random();

        foreach (var kv in records)
        {
            await using var memoryStream = new MemoryStream();
            await using var writer = new StreamWriter(memoryStream);

            var recordsToWrite = kv.Value;

            var writingRecordsTasks = recordsToWrite.Select(x =>
            {
                var randomLengthString = randomizer.Next(100, 5000);

                return writer.WriteLineAsync($"track-ref-{x.Tracking}, " +
                                             $"now in ticks: {DateTime.UtcNow.Ticks}, " +
                                             $"content: {new string(GetRandomLetter(), randomLengthString)}");
            });

            await Task.WhenAll(writingRecordsTasks);

            await writer.FlushAsync();

            resultAsBytes.AddRange(memoryStream.ToArray());
        }

        var content = Encoding.UTF8.GetString(resultAsBytes.ToArray());
        await File.WriteAllLinesAsync("PathToAFile.txt", new[] { content });

        Console.ReadLine();
    }

    public static char GetRandomLetter() => (char)('a' + new Random().Next(0, 26));
}
问题解答

关于记录丢失的疑问

你的代码实现确实存在引发记录丢失的风险,核心原因是**StreamWriter.WriteLineAsync并非线程安全方法**。当你用Select批量创建多个任务并通过Task.WhenAll并行执行时,多个WriteLineAsync调用会同时操作StreamWriter的内部缓冲区,这会导致缓冲区数据被覆盖、写入顺序混乱,极端情况下就会出现记录丢失的问题。

之所以本地难以复现,是因为并行操作的冲突概率和数据量、系统负载有关:小数据量下缓冲区操作速度快,冲突概率极低;只有在高并发、大数据量的场景下,才会大概率触发这类线程安全问题。

关于Resharper提示的疑问

Resharper的提示属于误判。你的代码中writer是用await using声明的,它的生命周期会覆盖到await Task.WhenAll(recordTasks)和await writer.FlushAsync()执行完成之后,不会在任务执行过程中被提前释放。任务捕获的writer变量在整个异步流程中都是有效的,不会引发对象已释放的异常。

修复建议

要解决记录丢失问题,必须保证StreamWriter的写入操作是串行执行的。你有两种可选方案:

  • 放弃并行,串行写入:直接遍历stringRecordsToWrite,逐个调用await writer.WriteLineAsync(r),这样能完全避免线程安全问题,代码更简单可靠。
  • 先并行处理再串行写入:如果需要并行处理数据(比如先做数据转换),可以先并行处理所有记录得到结果集合,再串行写入StreamWriter,既保留处理效率又避免写入冲突。

推荐方案一的代码示例:

await using var memoryStream = new MemoryStream();
await using var writer = new StreamWriter(memoryStream);

foreach (var r in stringRecordsToWrite)
{
    await writer.WriteLineAsync(r);
}

await writer.FlushAsync();

var result = memoryStream.ToArray();

内容的提问来源于stack exchange,提问作者AllmanTool

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 11:35:29