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

如何用protobuf-net多次序列化ConcurrentDictionary到同一文件并反序列化?

解决protobuf-net序列化/反序列化ConcurrentDictionary到同一文件的问题

一、序列化阶段(多次追加到文件)

直接连续调用Serializer.Serialize会导致多个protobuf数据粘在一起,反序列化时无法识别边界。正确做法是每次写入前先记录序列化数据的长度,再写入protobuf内容,为后续读取拆分做标记。

实现代码

using System.IO;
using System.Collections.Concurrent;
using ProtoBuf;

// 假设MyClass已正确标记[ProtoContract]、[ProtoMember]等特性
public class CommunicationRecorder
{
    private readonly string _filePath;

    public CommunicationRecorder(string filePath)
    {
        _filePath = filePath;
        if (!File.Exists(_filePath))
        {
            using (File.Create(_filePath)) {}
        }
    }

    public void RecordState(ConcurrentDictionary<string, MyClass> state)
    {
        using (var fs = new FileStream(_filePath, FileMode.Append, FileAccess.Write, FileShare.Read))
        {
            // 用内存流预序列化获取长度
            using (var ms = new MemoryStream())
            {
                Serializer.Serialize(ms, state);
                // 先写入数据长度(用Fixed32格式,protobuf-net原生支持)
                Serializer.SerializeWithLengthPrefix(fs, (int)ms.Length, PrefixStyle.Fixed32);
                // 写入实际序列化数据
                ms.Position = 0;
                ms.CopyTo(fs);
            }
        }
    }
}

二、反序列化阶段(读取文件重建状态序列)

读取时先读取每个数据块的长度标记,再按长度读取对应字节,反序列化为ConcurrentDictionary,依次获取每次序列化的状态。

实现代码

public class CommunicationPlayer
{
    private readonly string _filePath;

    public CommunicationPlayer(string filePath)
    {
        _filePath = filePath;
    }

    public IEnumerable<ConcurrentDictionary<string, MyClass>> Playback()
    {
        using (var fs = new FileStream(_filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
        {
            while (fs.Position < fs.Length)
            {
                // 先读取长度标记
                if (!Serializer.TryDeserializeWithLengthPrefix(fs, PrefixStyle.Fixed32, out int length))
                    break;
                
                // 读取对应长度的字节数据
                var buffer = new byte[length];
                fs.Read(buffer, 0, length);
                
                // 反序列化为字典
                using (var ms = new MemoryStream(buffer))
                {
                    yield return Serializer.Deserialize<ConcurrentDictionary<string, MyClass>>(ms);
                }
            }
        }
    }
}

三、关键注意事项

  • 自定义类型与继承处理:确保所有涉及的类型(包括MyClass、父类、嵌套类型)正确标记特性:
    • 基类需添加[ProtoInclude(子类型编号, typeof(子类型))],示例:
      [ProtoContract]
      [ProtoInclude(1, typeof(MyDerivedClass))]
      public class MyBaseClass
      {
          [ProtoMember(1)]
          public int Id { get; set; }
      }
      
      [ProtoContract]
      public class MyDerivedClass : MyBaseClass
      {
          [ProtoMember(2)]
          public string Name { get; set; }
      }
      
    • 所有需要序列化的属性/字段必须标记[ProtoMember(唯一编号)],编号不可重复。
  • 性能优化:
    • 高频写入时可复用FileStream,避免频繁打开关闭文件;
    • 直接使用Serializer.SerializeWithLengthPrefix序列化字典,替代手动写长度的步骤,代码更简洁:
      Serializer.SerializeWithLengthPrefix(fs, state, PrefixStyle.Fixed32);
      
      反序列化对应改为:
      while (Serializer.TryDeserializeWithLengthPrefix(fs, PrefixStyle.Fixed32, out ConcurrentDictionary<string, MyClass> state))
      {
          yield return state;
      }
      
  • 线程安全:ConcurrentDictionary读取本身线程安全,但如果序列化时字典有大量写入操作,可短暂加锁(如ReaderWriterLockSlim)保证状态一致性。

四、常见问题排查

若反序列化报错,检查以下点:

  • 序列化、反序列化时的类结构(特性标记、字段/属性、继承关系)是否完全一致;
  • 长度前缀的格式(如Fixed32)是否前后匹配;
  • 文件是否损坏(如写入中断导致最后一个数据块不完整),可添加校验和验证。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 23:05:23