【发布时间】:2018-08-05 17:23:25
【问题描述】:
我有一个向服务器发送查询请求的报告工具。服务器完成查询后,将结果发送回请求报告工具。通信是使用 WCF 完成的。
存储在 DataSet 对象中的查询数据非常大,通常大约 100mb 大。
为了加快传输我序列化(BinaryFormatter)并压缩DataSet。服务器和报告工具之间传输的对象是一个字节数组。
但是,在几次请求之后,报告工具在尝试反序列化 DataSet 时会抛出 OutOfMemoryException。调用时抛出异常:
dataSet = (DataSet) formatter.Deserialize(dstream);
dstream是用于解压传输的压缩字节数组的DeflateStream。
当从流中创建字节数组时,在 formatter.Deserialize 的子调用中发生异常。
有没有其他的二进制序列化方式有更好的机制来防止这个异常?
实施:
DataSet的序列化和压缩方法(服务端使用)
public static byte[] Compress(DataSet dataSet)
{
using (var input = new MemoryStream())
{
var binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(input, dataSet);
using (var output = new MemoryStream())
{
using (var compressor = new DeflateStream(output, CompressionLevel.Optimal))
{
input.Position = 0;
var buffer = new byte[1024];
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
compressor.Write(buffer, 0, read);
compressor.Close();
return output.ToArray();
}
}
}
}
DataSet的解压反序列化方法(报表工具使用)
public static DataSet Decompress(byte[] data)
{
DataSet dataSet;
using (var input = new MemoryStream(data))
{
using (var dstream = new DeflateStream(input, CompressionMode.Decompress))
{
var formatter = new BinaryFormatter();
dataSet = (DataSet) formatter.Deserialize(dstream);
}
}
return dataSet;
}
堆栈跟踪:
at System.Array.InternalCreate(Void* elementType, Int32 rank, Int32* pLengths, Int32* pLowerBounds)
at System.Array.CreateInstance(Type elementType, Int32 length)
at System.Array.UnsafeCreateInstance(Type elementType, Int32 length)
at System.Runtime.Serialization.Formatters.Binary.ObjectReader.ParseArray(ParseRecord pr)
at System.Runtime.Serialization.Formatters.Binary.ObjectReader.ParseObject(ParseRecord pr)
at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Parse(ParseRecord pr)
at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadArray(BinaryHeaderEnum binaryHeaderEnum)
at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.Run()
at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream)
at DRX.PTClientMonitoring.Infrastructure.Helper.DataSetCompressor.Decompress(Byte[] data) in c:\_develop\PTClientMonitoringTool\PTClientMonitoringTool\Source\DRX.PTClientMonitoring.Infrastructure\Helper\DataSetCompressor.cs:line 51
at DRX.PTClientMonitoring.Reporting.ViewModels.ShellViewModel.<>c__DisplayClassf.<ExecudeDefinedQuery>b__4() in c:\_develop\PTClientMonitoringTool\PTClientMonitoringTool\Source\DRX.PTClientMonitoring.Reporting\ViewModels\ShellViewModel.cs:line 347
【问题讨论】:
-
如果能分享相关代码会有帮助吗?
-
在发送端,您是否将
dataSet.RemotingFormat = SerializationFormat.Binary;设置为推荐的here?另外,您可以分享发生内存不足异常的回溯吗?我的印象是BinaryFormatter通常会以增量方式读取流。 -
看看服务器和报告工具使用的方法。实际反序列化之前的垃圾回收调用无法阻止异常。我使用的是 DataSet,因为它包含多个表,具体取决于报告工具的查询请求。
-
堆栈跟踪已添加。我已经设置了RemotingFormat,但是在一定数量的请求之后仍然会出现异常。
-
那么如果我冒昧地猜测一下,那是您在large object heap 上创建了太多对象,最终导致内存碎片和内存不足错误。大的
byte[] data数组是一个明显的候选者。有什么方法可以直接将DataSet流式传输,而不是加载到中间字节数组中?如果不把 Mark Gravell 的建议复制到某种 tempStream.
标签: c# dataset out-of-memory deserialization large-data