我一直在筛选帖子和论坛,但找不到实现这一目标的方法。
我有10,000,000个Person对象的数组。我使用流式WCF Net.Tcp Web服务通过网络发送这些对象。
问题是我想要读取第一个,例如,数组的5000个Person对象,因为它到达并仅处理那些。之后,我将推进流并阅读另一个5000等...
我还没有找到办法做到这一点,因为据我所知,C#中没有明确的对象大小。就像在,我不能只读取流的前312个字节并说"是的,这是第一个Person对象。现在阅读下一个312字节以获得下一个人。"。
我最好使用 ProtoBuf-Net 来序列化我的对象,但.NET BinaryFormatter 也可以。
我也打开以块的形式发送数据,例如5000的数组。但我想这样做,而不是每次都打开一个全新的tcp连接。如果只有一种方法可以告诉代码读取流:"好的,反序列化我刚发送给你的所有内容(数组5000)然后我将继续写另一个5000到流"。
有什么想法吗? 感谢。
答案 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。