protobuf-net collection序列化抛出StackOverflowException

时间:2016-03-17 16:23:28

标签: c# serialization windows-ce protobuf-net

我有一个使用protobuf-net序列化的嵌入式C#应用程序。 序列化大约50个条目的集合时,它会抛出StackOverflowException,只能在运行WinCE的设备上重现。 堆栈大约有600个条目,结尾如下:

at ProtoBuf.Meta.RuntimeTypeModel.GetKey(Type type, Boolean demand, Boolean getBaseKey)
   at ProtoBuf.Meta.ValueMember.TryGetCoreSerializer(RuntimeTypeModel model, DataFormat dataFormat, Type type, WireType& defaultWireType, Boolean asReference, Boolean dynamicType, Boolean overwriteList, Boolean allowComplexTypes)
   at ProtoBuf.Meta.ValueMember.BuildSerializer()
   at ProtoBuf.Meta.ValueMember.get_Serializer()
   at ProtoBuf.Meta.MetaType.BuildSerializer()
   at ProtoBuf.Meta.MetaType.get_Serializer()
   at ProtoBuf.Meta.RuntimeTypeModel.Serialize(Int32 key, Object value, ProtoWriter dest)
   at ProtoBuf.ProtoWriter.WriteObject(Object value, Int32 key, ProtoWriter writer)
   at ProtoBuf.Serializers.SubItemSerializer.ProtoBuf.Serializers.IProtoSerializer.Write(Object value, ProtoWriter dest)
   at ProtoBuf.Serializers.TagDecorator.Write(Object value, ProtoWriter dest)
   at ProtoBuf.Serializers.PropertyDecorator.Write(Object value, ProtoWriter dest)
   at ProtoBuf.Serializers.TypeSerializer.Write(Object value, ProtoWriter dest)
   at ProtoBuf.Meta.RuntimeTypeModel.Serialize(Int32 key, Object value, ProtoWriter dest)
   at ProtoBuf.ProtoWriter.WriteObject(Object value, Int32 key, ProtoWriter writer)
   at ProtoBuf.BclHelpers.WriteNetObject(Object value, ProtoWriter dest, Int32 key, NetObjectOptions options)
   at ProtoBuf.Serializers.NetObjectSerializer.Write(Object value, ProtoWriter dest)
   at ProtoBuf.Serializers.TagDecorator.Write(Object value, ProtoWriter dest)
   at ProtoBuf.Serializers.FieldDecorator.Write(Object value, ProtoWriter dest)
   at ProtoBuf.Serializers.TypeSerializer.Write(Object value, ProtoWriter dest)
   at ProtoBuf.Meta.RuntimeTypeModel.Serialize(Int32 key, Object value, ProtoWriter dest)
   at ProtoBuf.ProtoWriter.WriteObject(Object value, Int32 key, ProtoWriter writer)
   at ProtoBuf.Serializers.SubItemSerializer.ProtoBuf.Serializers.IProtoSerializer.Write(Object value, ProtoWriter dest)
   at ProtoBuf.Serializers.TagDecorator.Write(Object value, ProtoWriter dest)
   at ProtoBuf.Serializers.ListDecorator.Write(Object value, ProtoWriter dest)
   at ProtoBuf.Serializers.PropertyDecorator.Write(Object value, ProtoWriter dest)
   at ProtoBuf.Serializers.TypeSerializer.Write(Object value, ProtoWriter dest)
   at ProtoBuf.Meta.RuntimeTypeModel.Serialize(Int32 key, Object value, ProtoWriter dest)
   at ProtoBuf.ProtoWriter.WriteObject(Object value, Int32 key, ProtoWriter writer)
   at ProtoBuf.BclHelpers.WriteNetObject(Object value, ProtoWriter dest, Int32 key, NetObjectOptions options)
   at ProtoBuf.Serializers.NetObjectSerializer.Write(Object value, ProtoWriter dest)

列表定义如下:

[ProtoMember(2, AsReference = true, DataFormat = DataFormat.Group)]
        //[ProtoMember(2, AsReference = true)]
        public NodeList<T> Nodes { get; private set; }

(我试过两个版本,有和没有DataFormat.Group)

[ProtoContract(ImplicitFields = ImplicitFields.AllFields)]        
public class NodeList<T> : Collection<GraphNode<T>> where T : new()

在Windows 7上运行具有更多资源的应用程序时,不会抛出异常。

是否有可能优化序列化? 我做错了吗?

为什么调用堆栈这么长?我认为列表应该按顺序处理为数组,而不是递归调用?

谢谢, Daiana

1 个答案:

答案 0 :(得分:1)

您的问题中没有包含GraphNode<T>的示例。但是,从名称来看,它似乎是一般的图形节点元素,类似于Microsoft技术文章An Extensive Examination of Data Structures Using C# 2.0: Part 5: From Trees to Graphs中的GraphNode<T>NodeList<T>Graph<T>

在这种情况下,如果您的节点图非常深并且根节点位于集合的开始,那么很可能超过序列化节点列表的最大堆栈深度。 Protobuf-net: the unofficial manual

解释了这一点的原因
  

参考如何运作

     

当序列化应用了AsReference或AsReferenceDefault的类Foo时,协议缓冲区中字段的类型从Foo变为bcl.NetObjectProxy,其在protobuf-net的源代码中定义如下(Tools / bcl。原):

message NetObjectProxy {
  // for a tracked object, the key of the **first** 
  // time this object was seen
  optional int32 existingObjectKey = 1; 
  // for a tracked object, a **new** key, the first 
  // time this object is seen
  optional int32 newObjectKey = 2;
  // for dynamic typing, the key of the **first** time 
  // this type was seen
  optional int32 existingTypeKey = 3; 
  // for dynamic typing, a **new** key, the first time 
  // this type is seen
  optional int32 newTypeKey = 4;
  // for dynamic typing, the name of the type (only 
  // present along with newTypeKey) 
  optional string typeName = 8;
  // the new string/value (only present along with 
  // newObjectKey)
  optional bytes payload = 10;
}
     

所以看来

     
      
  • 遇到第一次对象,写入newObjectKey和有效负载字段;据推测,有效载荷就像存储一样   它的类型是Foo。
  •   
  • 当再次遇到该对象时,只写入existingObjectKey。
  •   

因此,如果图形恰好非常深,并且根作为列表中的第一个元素被遇到,则protobuf-net将递归地遍历图形深度优先,而不是遍历数组广度优先,从而溢出堆栈。

为了避免这个问题,您可以利用以下事实:当AsReference = true,protobuf-net仅在第一次遇到对象成员时迭代时,序列化NodeList<T>分两个阶段:

  1. 序列化节点列表,不包含其邻居信息
  2. 然后序列化邻居信息表。
  3. 例如,使用上述文章中的GraphNode<T>NodeList<T>Graph<T>的简化版本作为基础,可以通过以下方式实现:

    [ProtoContract]
    public class GraphNode<T> where T : new()
    {
        readonly NodeList<T> neighbors = new NodeList<T>();
    
        public GraphNode()
        {
            this.Value = new T();
        }
    
        public GraphNode(T value)
            : this()
        {
            this.Value = value;
        }
    
        [ProtoMember(1, AsReference = true)]
        public T Value { get; set; }
    
        // Do not serialize the list of neighbors directly!  
        // Instead this will be serialized by the NodeList<T> owned by the Graph<T>
        [ProtoIgnore]
        public NodeList<T> Neighbors { get { return neighbors; } }
    }
    
    [ProtoContract(IgnoreListHandling = true)]
    public class NodeList<T> : Collection<GraphNode<T>> where T : new()
    {
        [ProtoContract]
        class GraphNodeNeighborsProxy
        {
            [ProtoMember(1, AsReference = true)]
            public GraphNode<T> Node { get; set; }
    
            [ProtoMember(2, AsReference = true, DataFormat = DataFormat.Group)]
            public ICollection<GraphNode<T>> Neighbors
            {
                get
                {
                    return Node == null ? null : Node.Neighbors;
                }
            }
        }
    
        [ProtoMember(1, AsReference = true, DataFormat = DataFormat.Group)]
        IEnumerable<GraphNode<T>> Nodes
        {
            get
            {
                return new SerializationCollectionWrapper<GraphNode<T>, GraphNode<T>>(this, n => n, (c, n) => c.Add(n)); 
            }
        }
    
        [ProtoMember(2, DataFormat = DataFormat.Group)]
        IEnumerable<GraphNodeNeighborsProxy> NeighborsTable
        {
            get
            {
                return new SerializationCollectionWrapper<GraphNode<T>, GraphNodeNeighborsProxy>(
                    this,
                    n => new GraphNodeNeighborsProxy { Node = n },
                    (c, proxy) => {}
                    );
            }
        }
    }
    
    [ProtoContract]
    public class Graph<T> where T : new()
    {
        readonly private NodeList<T> nodeSet = new NodeList<T>();
    
        public Graph() { }
    
        public GraphNode<T> AddNode(GraphNode<T> node)
        {
            // adds a node to the graph
            nodeSet.Add(node);
            return node;
        }
    
        public GraphNode<T> AddNode(T value)
        {
            // adds a node to the graph
            return AddNode(new GraphNode<T>(value));
        }
    
        public void AddDirectedEdge(GraphNode<T> from, GraphNode<T> to)
        {
            from.Neighbors.Add(to);
        }
    
        [ProtoMember(2, AsReference = true, DataFormat = DataFormat.Group)]
        public NodeList<T> Nodes
        {
            get
            {
                return nodeSet;
            }
        }
    }
    
    public class SerializationCollectionWrapper<TFrom, TTo> : ICollection<TTo>
    {
        readonly ICollection<TFrom> collection;
        readonly Func<TFrom, TTo> mapTo;
        readonly Action<ICollection<TFrom>, TTo> add;
    
        public SerializationCollectionWrapper(ICollection<TFrom> collection, Func<TFrom, TTo> mapTo, Action<ICollection<TFrom>, TTo> add)
        {
            if (collection == null || mapTo == null || add == null)
                throw new ArgumentNullException();
            this.collection = collection;
            this.mapTo = mapTo;
            this.add = add;
        }
    
        ICollection<TFrom> Collection { get { return collection; } }
    
        #region ICollection<TTo> Members
    
        public void CopyTo(TTo[] array, int arrayIndex)
        {
            foreach (var item in this)
                array[arrayIndex++] = item;
        }
    
        public int Count
        {
            get { return Collection.Count; }
        }
    
        public bool IsReadOnly
        {
            get { return Collection.IsReadOnly; }
        }
    
        public void Add(TTo item)
        {
            add(Collection, item);
        }
    
        public void Clear()
        {
            throw new NotImplementedException();
        }
    
        public bool Contains(TTo item)
        {
            throw new NotImplementedException();
        }
    
        public bool Remove(TTo item)
        {
            throw new NotImplementedException();
        }
    
        #endregion
    
        #region IEnumerable<TTo> Members
    
        public IEnumerator<TTo> GetEnumerator()
        {
            foreach (var item in Collection)
                yield return mapTo(item);
        }
    
        #endregion
    
        #region IEnumerable Members
    
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    
        #endregion
    }
    

    请注意,这仅在根节点列表包含图中的所有节点时才有效。如果您没有图表中所有节点的表格,则需要计算图表的transitive closure以预先序列化所有节点。

相关问题