如何使用流以更低内存占用获取WCF消息体?
问题背景
现有如下XML文件:
<bookstore> <book genre='novel' ISBN='10-861003-324'><title>The Handmaid's Tale</title><price>19.95</price></book> <book genre='novel' ISBN='1-861001-57-5'><title>Pride And Prejudice</title><price>24.95</price></book> </bookstore>
使用该XML创建WCF的Message对象后,希望以低内存开销的方式获取消息体流,目前尝试了两种方案:
方案一:通过WriteBodyContents写入内存流
XmlReader xmlReader = XmlReader.Create(@"D:\TestForCSharp\ConsoleApp1\ConsoleApp1\2books.xml"); Message message = Message.CreateMessage(MessageVersion.None, null, xmlReader); MemoryStream ms = new MemoryStream(); XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter(ms, Encoding.UTF8); message.WriteBodyContents(writer); writer.Flush(); ms.Seek(0, SeekOrigin.Begin);
此方法比ReadOuterXml内存占用低,但message.WriteBodyContents(writer)仍会将整个消息体加载到内存中。
方案二:通过ReadOuterXml转换为流
XmlReader xmlReader = XmlReader.Create(@"D:\TestForCSharp\ConsoleApp1\ConsoleApp1\2books.xml"); Message message = Message.CreateMessage(MessageVersion.None, null, xmlReader); XmlReader reader = message.GetReaderAtBodyContents(); string res = reader.ReadOuterXml(); Stream ms = GenerateStreamFromString(res);
辅助方法:
public static Stream GenerateStreamFromString(string s) { var stream = new MemoryStream(); var writer = new StreamWriter(stream); writer.Write(s); writer.Flush(); stream.Position = 0; return stream; }
该方案内存开销更大,因为会先将整个消息体转为字符串再存入内存流。
优化方案
要实现接近零内存开销的流式处理,核心是避免将整个消息体加载到内存中,而是直接在流之间做传输:
1. 直接写入目标输出流(无内存中转)
如果最终需要将消息体写入某个外部流(如文件流、网络流),不要先写入MemoryStream,直接用目标流创建XmlDictionaryWriter:
using (XmlReader xmlReader = XmlReader.Create(@"D:\TestForCSharp\ConsoleApp1\ConsoleApp1\2books.xml")) using (Message message = Message.CreateMessage(MessageVersion.None, null, xmlReader)) // 替换为你的目标流,比如文件流 using (FileStream outputStream = new FileStream(@"D:\output.xml", FileMode.Create, FileAccess.Write)) using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter(outputStream, Encoding.UTF8)) { message.WriteBodyContents(writer); writer.Flush(); }
这样消息体内容会直接从原始XmlReader流向目标输出流,不会在内存中缓存整个内容,内存开销仅为XmlReader和Writer的缓冲区大小(默认很小)。
2. 使用管道流实现流式获取(无需内存缓存)
如果必须得到一个可读取的流对象(而非直接写入外部流),可以用PipeStream实现双向流式传输,让WriteBodyContents直接写入管道的一端,另一端作为可读取的流使用,全程无内存缓存:
using (XmlReader xmlReader = XmlReader.Create(@"D:\TestForCSharp\ConsoleApp1\ConsoleApp1\2books.xml")) using (Message message = Message.CreateMessage(MessageVersion.None, null, xmlReader)) using (var pipeServer = new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.None)) using (var pipeClient = new AnonymousPipeClientStream(PipeDirection.In, pipeServer.ClientSafePipeHandle)) { // 异步写入管道(避免阻塞) Task.Run(() => { using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter(pipeServer, Encoding.UTF8)) { message.WriteBodyContents(writer); writer.Flush(); pipeServer.WaitForPipeDrain(); pipeServer.Close(); } }); // 此时pipeClient就是可读取的消息体流,直接使用即可 // 示例:读取流内容到控制台 using (StreamReader reader = new StreamReader(pipeClient)) { Console.WriteLine(reader.ReadToEnd()); } }
这种方式下,消息体内容会实时从XmlReader通过管道流向pipeClient,内存仅用于管道的缓冲区,几乎可以忽略。
3. 避免Message的内部缓冲
创建Message时,默认可能会缓冲内容。确保传入的XmlReader是流式非缓冲的(从文件创建的XmlReader默认满足),同时避免调用会触发全量加载的方法(如ReadOuterXml),坚持流式处理逻辑即可。
关键说明
- WCF的
Message对象本身在创建时,如果传入的是流式XmlReader,不会立即加载整个内容到内存,但调用WriteBodyContents或GetReaderAtBodyContents时,若写入内存流或读取为字符串,会导致整个内容加载到内存。 - 零内存开销的核心是流式传递:让数据从原始源(文件流)直接流向目标流,中间不经过内存缓存。
内容的提问来源于stack exchange,提问作者Water
相关产品推荐
相关产品推荐

