2016-03-17 3 views
2

У меня есть встроенное приложение C#, использующее серию передачи protobuf-net. При сериализации коллекции из примерно 50 записей она генерирует исключение StackOverflowException, воспроизводимое только на устройстве, которое запускает WinCE. Стек около 600 записей глубокий и заканчивается так:protobuf-net collection serialization throws StackOverflowException

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

+0

Что делает 'GraphNode ' похожим? Это действительно какая-то рекурсивная структура данных? Действительно ли 'NodeList ' содержит сложное дерево или граф объектов? – dbc

+0

Да, это может быть довольно сложная структура, но не содержит дерева или графа. Кроме того, стандартная серализация XML отлично работает. –

ответ

1

Вы не включают пример вашего GraphNode<T> в вашем вопросе. Однако из имени он будет выглядеть как общий элемент узла графа, аналогичный GraphNode<T>, NodeList<T> и Graph<T> из технической статьи Microsoft An Extensive Examination of Data Structures Using C# 2.0: Part 5: From Trees to Graphs.

В этом случае, если граф узла очень глубокий, а корневой узел находится на , начиная с коллекции, то вполне возможно превысить максимальную глубину стека, упорядочивая список узлов. Причина этого объясняется в Protobuf-net: the unofficial manual:

Как работают ссылки

При сериализации класса Foo, к которому относится AsReference или AsReferenceDefault, тип поля в изменении буфера протокола из Foo в BCL .NetObjectProxy, которая определяется следующим образом в исходном коде Protobuf-сети (Tools/bcl.proto):

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; 
} 

Получается, что

  • первый раз объект встречается, записываются новые объекты ObjectKey и поля полезной нагрузки; предположительно, полезная нагрузка сохраняется, как если бы его тип Foo.
  • Когда объект встречается снова, записывается только существующий ObjectKey.

Таким образом, если граф бывает очень глубоким и корень встречается в качестве первого элемента в списке, Protobuf-сеть будет рекурсивно пройти через глубину первой итерации, а не через массив граф breadth- во-первых, и тем самым переполнять стек.

Чтобы избежать этой проблемы, вы можете воспользоваться тем, что, когда AsReference = true, Protobuf-сеть только итерацию хотя члены объекта первый раз, когда он встречается, чтобы сериализовать NodeList<T> в два этапа:

  1. Сериализовать список узлов без информации о себе.
  2. Затем сериализуйте таблицу соседей.

Например, с использованием упрощенных версий 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, чтобы сериализовать все узлы вверх.

+1

В конце я использовал более простой подход, я сериализовал в списке соседей только уникальные идентификаторы узлов, а не весь объект узла, и он работал как шарм. Переполнение стека происходило при рекурсивной сериализации соседей на очень глубоком графике. Большое спасибо! –