ProtoBuf-Net 序列化中的 OutOfMemoryException

OutOfMemoryException in ProtoBuf-Net Serialization

我通过protobuf-net将一些大Dictionary序列化到磁盘,磁盘文件大小为450MB。

通常,代码因 OutOfMemoryException 而失败

System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown.
   at ProtoBuf.BufferPool.ResizeAndFlushLeft(Byte[]& buffer, Int32 toFitAtLeastBytes, Int32 copyFromIndex, Int32 copyBytes) in c:\Dev\protobuf-net\protobuf-net\BufferPool.cs:line 60
   at ProtoBuf.ProtoWriter.StartSubItem(Object instance, ProtoWriter writer, Boolean allowFixed) in c:\Dev\protobuf-net\protobuf-net\ProtoWriter.cs:line 341
   at ProtoBuf.ProtoWriter.WriteObject(Object value, Int32 key, ProtoWriter writer) in c:\Dev\protobuf-net\protobuf-net\ProtoWriter.cs:line 44
   at proto_15(Object , ProtoWriter )
   at ProtoBuf.ProtoWriter.WriteObject(Object value, Int32 key, ProtoWriter writer, PrefixStyle style, Int32 fieldNumber) in c:\Dev\protobuf-net\protobuf-net\ProtoWriter.cs:line 116
   at ProtoBuf.Meta.TypeModel.SerializeWithLengthPrefix(Stream dest, Object value, Type type, PrefixStyle style, Int32 fieldNumber, SerializationContext context) in c:\Dev\protobuf-net\protobuf-net\Meta\TypeModel.cs:line 548
   at ProtoBuf.Meta.TypeModel.SerializeWithLengthPrefix(Stream dest, Object value, Type type, PrefixStyle style, Int32 fieldNumber) in c:\Dev\protobuf-net\protobuf-net\Meta\TypeModel.cs:line 515
   at ProtoBuf.Serializer.SerializeWithLengthPrefix[T](Stream destination, T instance, PrefixStyle style, Int32 fieldNumber) in c:\Dev\protobuf-net\protobuf-net\Serializer.cs:line 352

机器有 20GB 以上的可用内存。

而我的进程只占用4.6GB

并且 app.config 针对大内存进行了调整

<runtime>
    <gcAllowVeryLargeObjects enabled="true" />
    <gcServer enabled="true"/>
</runtime>

目前我使用 try-catch 来捕获异常并在几秒后重试。而且它们通常有效。

有没有办法避免这个异常?

gcAllowVeryLargeObjects 对字节缓冲区没有太大帮助,因为元素数量限制仍然适用——但是,令人好奇的是它在 450MB 时突破了;不要误会我的意思——那是相当大的。你在序列化到一个文件吗?网络?或者只是在内存中?如果它不是纯内存中的,可能有一些方法可以说服它需要更少的内部缓冲——例如,切换到组而不是子消息(这只需要一些属性更改)。例如:this

public class Foo {
     [ProtoMember(1)]
     public List<Bar> Bars {get; set;}
}

将需要在内存中缓冲 Bar 数据,其中-as:

public class Foo {
     [ProtoMember(1, DataFormat = DataFormat.Group)]
     public List<Bar> Bars {get; set;}
}

不会。

我们在试图通过 WCF 将 300 万条记录从服务端传递到 WPF 客户端的应用程序中遇到了同样的问题。

解决方案是重建服务端,其中序列化为 64 位应用程序(不是 AnyCPU,而是 x64),并确保 APPPool 在 64 位模式下为 运行("Allow 32 bit applications" 设置为 False)。

尽情享受吧!