C#中MessagePack序列化结构体列表追加写入二进制文件的问题
关于MessagePack序列化时序数据到磁盘的问题解答
首先咱们先解决你当前遇到的文件大小为0字节的问题——你的代码里犯了一个很容易忽略的小错误:你生成了序列化后的byte[],但根本没把它写入到FileStream里!对比BinaryFormatter的代码,它是直接把对象序列化到流里,而你的MessagePack代码只完成了序列化步骤,却漏了写入流的操作。
修复当前代码的两种方式
方式1:直接序列化到流(更高效,无需额外byte数组)
public struct struct_realTime { public int indexNum { get; set; } public string currentTime { get; set; } public string currentType { get; set; } } class Program { static void Main(string[] args) { List<struct_realTime> list_temp = new List<struct_realTime>(100000); for (int num=0; num < 100000; num++) { list_temp.Add(new struct_realTime { indexNum = 1, currentTime = "time", currentType = "type" }); } string filename = "file.bin"; // 直接序列化到FileStream,跳过byte数组环节 using (var fileStream = new FileStream(filename, FileMode.Append, FileAccess.Write)) { MessagePackSerializer.Serialize(fileStream, list_temp); // 如果你需要验证,可以把流指针移到开头再反序列化测试 // fileStream.Position = 0; // var testList = MessagePackSerializer.Deserialize<List<struct_realTime>>(fileStream); } } }
方式2:先序列化到byte数组,再写入流
如果你确实需要先拿到byte数组做其他处理,记得加上写入流的代码:
using (var fileStream = new FileStream(filename, FileMode.Append, FileAccess.Write)) { byte[] bytes = MessagePackSerializer.Serialize(list_temp); Console.WriteLine(MessagePackSerializer.ToJson(bytes)); // 关键:把byte数组写入流 fileStream.Write(bytes, 0, bytes.Length); }
问题1:异步序列化写入是否更优?
在你这个实时接收时序数据+定期批量写入的场景下,异步操作绝对是更优的选择。原因很简单:
- 实时数据接收不能被阻塞,如果用同步写入,当磁盘IO比较慢的时候,主线程会卡在写入操作上,可能导致后续的实时数据无法及时接收,甚至丢数据。
- 异步操作会把序列化和磁盘IO放到后台线程执行,主线程可以继续专注于接收新的时序数据,保证数据接收的实时性。
- 对于批量数据(比如100条一次),异步IO的性能优势会更明显,尤其是在高并发或者磁盘压力大的环境下。
问题2:如何用MessagePack实现简易磁盘写入(含异步版本)
这里给你一个更贴合你需求的实现——定期收集数据,达到阈值后异步写入磁盘,同时注意MessagePack的配置(建议给结构体加上[MessagePackObject]属性,避免序列化异常,尤其是复杂结构体):
第一步:给结构体添加MessagePack标记(可选但推荐)
[MessagePackObject] public struct struct_realTime { [Key(0)] public int indexNum { get; set; } [Key(1)] public string currentTime { get; set; } [Key(2)] public string currentType { get; set; } }
如果你不想手动加标记,可以使用
ContractlessStandardResolver,但显式标记的序列化性能更好,也更稳定。
第二步:异步批量写入的实现示例
class Program { private static readonly List<struct_realTime> _dataBuffer = new List<struct_realTime>(100); private const int BatchSize = 100; private static readonly string _filePath = "timedata.bin"; static async Task Main(string[] args) { // 模拟实时接收数据的循环 for (int i = 0; i < 1000; i++) { // 接收一条实时数据 var newData = new struct_realTime { indexNum = i, currentTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), currentType = "type_" + (i % 5) }; lock (_dataBuffer) { _dataBuffer.Add(newData); // 达到批量阈值,触发异步写入 if (_dataBuffer.Count >= BatchSize) { // 复制一份数据,避免写入时被修改 var batchData = _dataBuffer.ToList(); _dataBuffer.Clear(); // 异步写入,不阻塞主线程 await WriteBatchToDiskAsync(batchData); Console.WriteLine($"已写入{BatchSize}条数据"); } } // 模拟数据接收间隔 await Task.Delay(10); } // 程序结束前,写入剩余的不足一批的数据 lock (_dataBuffer) { if (_dataBuffer.Count > 0) { await WriteBatchToDiskAsync(_dataBuffer.ToList()); Console.WriteLine($"已写入剩余{_dataBuffer.Count}条数据"); } } } private static async Task WriteBatchToDiskAsync(List<struct_realTime> batchData) { try { // 使用异步FileStream打开文件,追加模式 using (var fileStream = new FileStream(_filePath, FileMode.Append, FileAccess.Write, FileShare.Read, 4096, true)) { // 异步序列化到流 await MessagePackSerializer.SerializeAsync(fileStream, batchData); // 确保数据写入磁盘(可选,根据可靠性需求) await fileStream.FlushAsync(); } } catch (Exception ex) { Console.WriteLine($"写入失败:{ex.Message}"); // 这里可以根据需求处理失败,比如把失败的数据缓存起来重试 } } }
这个实现的关键点:
- 用
lock保护数据缓冲区,因为实时接收和批量写入可能在不同线程执行。 - 异步写入时复制缓冲区数据,避免原列表被修改导致序列化异常。
- 使用
SerializeAsync和异步FileStream,完全不阻塞主线程。 - 程序结束时处理剩余数据,避免丢数据。
内容的提问来源于stack exchange,提问作者maynull
相关产品推荐
相关产品推荐

