在元素可用时反序列化对象数组的二进制数据

时间:2017-01-12 21:02:04

标签: c# wcf serialization stream

我一直在筛选帖子和论坛,但找不到实现这一目标的方法。

我有10,000,000个Person对象的数组。我使用流式WCF Net.Tcp Web服务通过网络发送这些对象。

问题是我想要读取第一个,例如,数组的5000个Person对象,因为它到达并仅处理那些。之后,我将推进流并阅读另一个5000等...

我还没有找到办法做到这一点,因为据我所知,C#中没有明确的对象大小。就像在,我不能只读取流的前312个字节并说"是的,这是第一个Person对象。现在阅读下一个312字节以获得下一个人。"。

我最好使用 ProtoBuf-Net 来序列化我的对象,但.NET BinaryFormatter 也可以。

我也打开以块的形式发送数据,例如5000的数组。但我想这样做,而不是每次都打开一个全新的tcp连接。如果只有一种方法可以告诉代码读取流:"好的,反序列化我刚发送给你的所有内容(数组5000)然后我将继续写另一个5000到流"。

有什么想法吗? 感谢。

2 个答案:

答案 0 :(得分:1)

.NET中的大多数对象可能没有明确的大小,但您可以找到序列化对象的大小。首先发送序列化对象的大小(以字节为单位),然后发送序列化对象。

// psuedo-code
byte[] serializedObj = DoSerialization(Person);    // we see length on an array

using (var writer = new StreamWriter(stream)) {
    writer.Write(serializedObj.Length);
    stream.Write(serializedObj);
}

您也可以通过修改发送对象的内容和方式批量执行此操作。您可以创建List<Person>,添加N个Person,序列化列表并像以前一样发送。

虽然我不确定在发送数据之前是否需要发送大小,但是在读取流时可以提供帮助,以了解您期望的字节数。

答案 1 :(得分:1)

只需在接收系统中使用ObservableCollection<Person>,即可使用protobuf-net执行此操作。在反序列化期间,当集合大于5000个对象时,删除并处理ObservableCollection<T>.CollectionChanged回调中的项目。然后处理[OnDeserialized]回调中的所有剩余项目。

例如,请考虑以下根对象:

[ProtoContract]
public class RootObject
{
    public RootObject()
    {
        this.People = new ObservableCollection<Person>();
    }

    [ProtoMember(1)]
    public ObservableCollection<Person> People { get; private set; }

    public event EventHandler<EventArgs<StreamingContext>> OnDeserialized;

    [OnDeserialized]
    internal void OnDeserializedMethod(StreamingContext context)
    {
        var onDeserialized = OnDeserialized;
        if (onDeserialized != null)
            onDeserialized(this, new EventArgs<StreamingContext> { Value = context });
    }
}

public class EventArgs<T> : EventArgs
{
    public T Value { get; set; }
}

假设您有一个方法要调用它来处理每个5000 Person个对象,因为它们被添加到集合中,例如:

const int ProcessIncrement = 5000;

void ProcessItems(ICollection<Person> people, bool force)
{
    if (people == null || people.Count == 0)
        return;
    if (people.Count >= ProcessIncrement || force)
    {
        // Remove and process the items, possibly on a different thread.
        Console.WriteLine(string.Format("Processing {0} people." people.Count));
        people.Clear();
    }
}

您可以预先分配RootObject并使用必要的逻辑添加侦听器,并将序列化流的内容合并到根目录中:

// Allocate a new RootObject
var newRoot = new RootObject();

// Add listeners to process chunks of Person objects as they are added
newRoot.People.CollectionChanged += (o, e) =>
    {
        // Process each chunk of 5000.
        var collection = (ICollection<Person>)o;
        ProcessItems(collection, false);
    };

newRoot.OnDeserialized += (o, e) =>
    {
        // Forcibly process any remaining no matter how many.
        ProcessItems(((RootObject)o).People, true);
    };

// Deserialize from the stream onto the pre-allocated newRoot
Serializer.Merge(stream, newRoot);

根据需要,每次将对象添加到集合中时都会调用ProcessItems,以5000为增量处理它们,然后无条件地处理剩余部分。

现在,唯一的问题是,protobuf-net在反序列化集合之前是否将整个流加载到内存中,还是进行流式反序列化?事实证明,它会执行后者,如此sample fiddle所示,当People集合中的项目被添加,处理和删除时,它会逐渐增加流位置。

这里我在反序列化之前手动将监听器添加到RootObject。如果您要在构造函数本身中添加它们,则可以将ProtoBuf.Serializer.Deserialize<RootObject>(Stream stream)而不是Serializer.Merge用于预先分配的根对象,这可能更容易集成到您当前的体系结构中。

顺便说一下,这种技术也适用于XmlSerializer和Json.NET。