异步加载XDocument

问题描述:

我想将大型XML文档加载到XDocument对象中。 使用XDocument.Load(path, loadOptions)的简单同步方法效果很好,但是在加载大文件(特别是从网络存储)时在GUI上下文中阻塞了很长时间。异步加载XDocument

我写了这个异步版本,旨在提高文档加载的响应速度,特别是通过网络加载文件时。

public static async Task<XDocument> LoadAsync(String path, LoadOptions loadOptions = LoadOptions.PreserveWhitespace) 
    { 
     String xml; 

     using (var stream = File.OpenText(path)) 
     { 
      xml = await stream.ReadToEndAsync(); 
     } 

     return XDocument.Parse(xml, loadOptions); 
    } 

但是,在从本地磁盘加载的200 MB XML原始文件中,同步版本会在几秒钟内完成。异步版本(在32位的上下文中运行),而不是将引发OutOfMemoryException

at System.Text.StringBuilder.ToString() 
    at System.IO.StreamReader.<ReadToEndAsyncInternal>d__62.MoveNext() 

我想象这是因为用于在内存中保留原始XML由XDocument解析临时字符串变量。假设在同步场景中,XDocument.Load()能够通过源文件进行流式传输,并且从不需要创建一个巨大的字符串来保存整个文件。

有没有什么办法让两全其美?加载XDocument完全异步I/O,而不需要创建一个大的临时字符串?

+0

也许你应该使用'XDocument.Load(流)'? – DavidG

+0

这会如何使加载操作异步? – Hydrargyrum

+0

那么它本身不会,但它会消除你在这里的字符串变量,并希望OOM异常。 – DavidG

首先,任务不是异步运行的。您需要使用内置的异步IO命令或自己启动线程池上的任务。例如

public static async Task<XDocument> LoadAsync 
(String path 
, LoadOptions loadOptions = LoadOptions.PreserveWhitespace 
) 
{ 
    return Task.Run(()=>{ 
    using (var stream = File.OpenText(path)) 
     { 
      return XDocument.Load(stream, loadOptions); 
     } 
    }); 
} 

如果您使用Parse的stream version,那么您不会得到一个临时字符串。

+3

好的。这就是我在对问题的最终评论中所概述的内容。因此,这将使用线程池线程来驱动隐式所需的I/O,因为XDocument在流中咀嚼。而且I/O本身会零星地阻塞Task的工作线程。 看起来这是最好的,可以做到的,因为没有一个真正的XDocument.LoadAsync()实现,它使用适当的异步I/O指令。 虽然我没有看到明确调用File.OpenText的好处。也可以直接调用XDocument.Load(path) – Hydrargyrum

+0

如果你正在并行读取服务器上数以千计的XDocuments 10s,你可能会担心从线程池中窃取线程而不是使用真正的异步IO,但这真的是一个问题? – bradgonesurfing

+1

可能不是。因此,我的评论是这可能够好。无论如何我都赞成并接受 – Hydrargyrum