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

为何StreamWriter异步写入时偶发NullReferenceException且未捕获?

偶发StreamWriter空引用异常排查(CAN总线数据记录场景)

我在开发CAN总线数据记录功能时,通过FrameReceived方法将接收的CAN帧存入列表,再调用StartRecord/StopRecord触发文件写入流程。目前遇到偶发的NullReferenceException,异常指向await writer.WriteLineAsync(line)代码行,但已确认line非空、writer初始化正常,同时疑惑为何异常未被内部try-catch捕获,以及空引用的对象到底是什么。


数据收集相关代码

private List<string> recordList = new List<string>();
private List<string> recordList1 = new List<string>();
private List<string> recordList2 = new List<string>();

public void FrameReceived(CANFrame frame)
{
    if (!isRecord) return;
    if (frame != null)
    {
        string record = RecordFrame(frame, "Rx");
        recordList.Add(record);
        if (frame.Channel == 1) recordList1.Add(record);
        if (frame.Channel == 2) recordList2.Add(record);
    }
}

public string RecordFrame(CANFrame frame, string type)
{
    string text1 = string.Empty;
    if (frame.type == TPCANMessageType.PCAN_MESSAGE_STANDARD)
    {
        text1 = ($"{stopwatch.ElapsedTicks * 1.0 / Stopwatch.Frequency,11:#0.000000} {frame.Channel}  {frame.Id,-15:X} {type}   d {frame.Length} {frame.ToHexString()}");
    }
    else if (frame.type == TPCANMessageType.PCAN_MESSAGE_EXTENDED)
    {
        text1 = ($"{stopwatch.ElapsedTicks * 1.0 / Stopwatch.Frequency,11:#0.000000} {frame.Channel}  " + $"{frame.Id:X}x".PadRight(15) + $" {type}   d {frame.Length} {frame.ToHexString()}");
    }
    else if (frame.type == TPCANMessageType.PCAN_MESSAGE_FD)
    {
        text1 = ($"{stopwatch.ElapsedTicks * 1.0 / Stopwatch.Frequency:#0.000000} CANFD {frame.Channel} {type} {frame.Id:X} 0 0 1 {frame.DLC:X2} {frame.Length} {frame.ToHexString()} 0 0 39b5 0 0 0 0");
    }
    else
    {
        if (frame.Data.Length <= 8)
        {
            text1 = ($"{stopwatch.ElapsedTicks * 1.0 / Stopwatch.Frequency,11:#0.000000} {frame.Channel}  " + (frame.Id > 0x7FF ? $"{frame.Id:X}x".PadRight(15) : $"{frame.Id,-15:X}") + $" {type}   d {frame.Length} {frame.ToHexString()}");
        }
        else
        {
            text1 = ($"{stopwatch.ElapsedTicks * 1.0 / Stopwatch.Frequency:#0.000000} CANFD {frame.Channel} {type} " + (frame.Id > 0x7FF ? $"{frame.Id:X}x" : $"{frame.Id:X}") + $" 0 0 1 {frame.DLC:X2} {frame.Length} {frame.ToHexString()} 0 0 39b5 0 0 0 0");
        }
    }
    return text1;
}

记录启停与文件写入代码

public void StartRecord(string fileName)
{
    this.fileName = fileName;
    stopwatch.Reset();
    stopwatch.Start();

    recordList.Clear();
    recordList1.Clear();
    recordList2.Clear();
    isRecord = true;
}

public void StopRecord()
{
    isRecord = false;
    WriteToFile();
    stopwatch.Stop();
}

private async void WriteToFile()
{
    try
    {
        Task writeAll = WriteToFileAsync($"{fileName}\\合并CAN.asc", new List<string>(recordList));
        Task write1 = WriteToFileAsync($"{fileName}\\内CAN.asc", new List<string>(recordList1));
        Task write2 = WriteToFileAsync($"{fileName}\\外CAN.asc", new List<string>(recordList2));
        await Task.WhenAll(writeAll, write1, write2);
    }
    catch(Exception ex)
    {
        SmartFrameWork.Console.WriteLine("WriteToFile()");
        SmartFrameWork.Console.WriteLine(ex.ToString());
    }
}

async Task WriteToFileAsync(string fileName,List<string> lines)
{
    try
    {
        using (StreamWriter writer = new StreamWriter(fileName))
        {
            await writer.WriteLineAsync($"date {DateTime.Now.ToString("dd/M/yyyy HH:mm:ss.fff")}");
            await writer.WriteLineAsync("base hex  timestamps absolute");
            await writer.WriteLineAsync("no internal events logged");
            await writer.WriteLineAsync("//Version 5.6");
            foreach (var line in lines)
            {
                if (writer == null)
                    SmartFrameWork.Console.WriteLine("writer为空");
                if (line == null)
                    SmartFrameWork.Console.WriteLine("写入该行的内容为空");
                await writer.WriteLineAsync(line);
            }
            await writer.FlushAsync(); 
        }
    }
    catch(Exception ex)
    {
        SmartFrameWork.Console.WriteLine("WriteToFileAsync(string fileName,List<string> lines)");
        SmartFrameWork.Console.WriteLine(ex.ToString());
    }
}

异常信息

System.NullReferenceException:未将对象引用设置到对象的实例。 在
System.IO.StreamWriter.d_55.MoveNext()
---引发异常的上一位置中堆栈跟踪的末尾· 在
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task
task) 在
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task
task) 在 System.Runtime.CompilerServices.TaskAwaiter.GetResult() 在
CANBusDriver.FileFrameFilter.d_35.MoveNext() 位置
D:\Projects\HIL\HIL_fanqingyu\V5.6Beta\HIL\CANdriver\FileFrameFilter.cs:行号157
--引发异常的上一位置中堆栈跟踪的末尾-- 在
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task
task) 在
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task
task) 在 System.Runtime.CompilerServices.TaskAwaiter.GetResult() 在
CANBusDriver.FileFrameFilter.d_34.MoveNext() 位置
D:\Projects\HIL\HIL_fanqingyu\V5.6Beta\HIL\CANdriver\FileFrameFilter.cs:行号 145
--引发异常的上一位置中堆栈跟踪的末尾-- 在
System.Runtime.CompilerServices.AsyncMethodBuilderCore.<>c.b__6_1(Object
state) 在 System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state) 在
System.Threading.ExecutionContext.RunInternal(ExecutionContext
executionContext, ContextCallback callback, Object state, Boolean
preserveSyncCtx) 在
System.Threading.ExecutionContext.Run(ExecutionContext
executionContext,ContextCallback callback, Object state, Boolean
preserveSyncCtx) 在
System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
在 System.Threading.ThreadPoolWorkQueue.Dispatch() 在
System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()


问题分析与解决方案

1. 异常未被捕获的原因

WriteToFile是async void方法,这类方法的异常无法被上层try-catch正确捕获,会直接抛到调用上下文的同步上下文,导致异常逃逸。此外,异步操作的上下文切换可能导致StreamWriter内部异常在using块释放后触发,绕过当前方法的try-catch。

2. 空引用的可能对象

  • StreamWriter底层流:文件系统异常(如文件被占用、磁盘空间不足、权限变更)可能导致底层流被意外关闭,此时StreamWriter内部引用变为null,触发空引用异常。
  • 异步状态失效:async void方法无返回任务,无法跟踪异步操作状态,可能导致StopRecord中的stopwatch.Stop()与文件写入操作并发,间接破坏StreamWriter的内部状态。

3. 修复建议

(1)将async void改为async Task并等待

修改方法签名,确保异步操作的异常能被正确捕获:

public async Task StopRecord()
{
    isRecord = false;
    await WriteToFile();
    stopwatch.Stop();
}

private async Task WriteToFile()
{
    try
    {
        Task writeAll = WriteToFileAsync($"{fileName}\\合并CAN.asc", new List<string>(recordList));
        Task write1 = WriteToFileAsync($"{fileName}\\内CAN.asc", new List<string>(recordList1));
        Task write2 = WriteToFileAsync($"{fileName}\\外CAN.asc", new List<string>(recordList2));
        await Task.WhenAll(writeAll, write1, write2);
    }
    catch(Exception ex)
    {
        SmartFrameWork.Console.WriteLine("WriteToFile()");
        SmartFrameWork.Console.WriteLine(ex.ToString());
    }
}

(2)增加文件操作防御与异常细化

在WriteToFileAsync中增加前置检查,细化异常捕获:

async Task WriteToFileAsync(string fileName,List<string> lines)
{
    try
    {
        // 确保目录存在
        var dir = Path.GetDirectoryName(fileName);
        if (!Directory.Exists(dir))
        {
            Directory.CreateDirectory(dir);
        }
        // 检查文件是否可写入
        if (File.Exists(fileName) && (File.GetAttributes(fileName) & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
        {
            SmartFrameWork.Console.WriteLine($"文件只读无法写入:{fileName}");
            return;
        }

        using (StreamWriter writer = new StreamWriter(fileName))
        {
            await writer.WriteLineAsync($"date {DateTime.Now.ToString("dd/M/yyyy HH:mm:ss.fff")}");
            await writer.WriteLineAsync("base hex  timestamps absolute");
            await writer.WriteLineAsync("no internal events logged");
            await writer.WriteLineAsync("//Version 5.6");
            
            foreach (var line in lines)
            {
                // 空行防御
                if (string.IsNullOrWhiteSpace(line))
                {
                    SmartFrameWork.Console.WriteLine("跳过空行写入");
                    continue;
                }
                // 流状态检查
                if (writer == null || writer.BaseStream == null || !writer.BaseStream.CanWrite)
                {
                    SmartFrameWork.Console.WriteLine($"写入流已失效:{fileName}");
                    break;
                }
                await writer.WriteLineAsync(line);
            }
            await writer.FlushAsync(); 
        }
    }
    catch(IOException ioEx)
    {
        SmartFrameWork.Console.WriteLine($"文件IO异常:{fileName}");
        SmartFrameWork.Console.WriteLine(ioEx.ToString());
    }
    catch(NullReferenceException nrEx)
    {
        SmartFrameWork.Console.WriteLine($"空引用异常:{fileName}");
        SmartFrameWork.Console.WriteLine(nrEx.ToString());
    }
    catch(Exception ex)
    {
        SmartFrameWork.Console.WriteLine($"写入文件异常:{fileName}");
        SmartFrameWork.Console.WriteLine(ex.ToString());
    }
}

(3)临时用同步写入验证

若异步问题难以排查,可临时改用同步写入,验证是否为异步上下文导致的问题:

void WriteToFileSync(string fileName,List<string> lines)
{
    try
    {
        var dir = Path.GetDirectoryName(fileName);
        if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
        
        using (StreamWriter writer = new StreamWriter(fileName))
        {
            writer.WriteLine($"date {DateTime.Now.ToString("dd/M/yyyy HH:mm:ss.fff")}");
            writer.WriteLine("base hex  timestamps absolute");
            writer.WriteLine("no internal events logged");
            writer.WriteLine("//Version 5.6");
            
            foreach (var line in lines)
            {
                if (!string.IsNullOrWhiteSpace(line))
                {
                    writer.WriteLine(line);
                }
            }
            writer.Flush(); 
        }
    }
    catch(Exception ex)
    {
        SmartFrameWork.Console.WriteLine($"同步写入异常:{fileName}");
        SmartFrameWork.Console.WriteLine(ex.ToString());
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 18:47:02