我希望将大型XML文档加载到XDocument对象中。使用XDocument.Load(path, loadOptions)的简单同步方法效果很好,但在加载大型文件(特别是从网络存储中)时,会在GUI上下文中阻塞很长时间。
我编写这个异步版本的目的是提高文档加载的响应能力,特别是在通过网络加载文件时。
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()能够在源文件中进行流处理,并且永远不需要创建一个巨大的字符串来保存整个文件。
有什么办法让这两个世界都占优势吗?用完全异步I/O加载XDocument,而不需要创建大的临时字符串?
发布于 2021-01-21 12:36:58
延迟回答,但我也需要在“遗留”.NET框架版本上读取异步,所以我想出了一种方法,可以以异步的方式真正读取内容,而不需要恢复到内存中缓冲XML数据。
由于XDocument.CreateWriter()提供的写入器不支持异步写入,因此XmlWriter.WriteNodeAsync()失败,所以代码执行异步读取并将其转换为同步写入XDocument-writer。然而,代码的灵感来自于XmlWriter.WriteNodeAsync()的工作方式。由于作者构建了内存中的DOM,这实际上比实际执行异步写入更好。
public static async Task<XDocument> LoadAsync(Stream stream, LoadOptions loadOptions) {
using (var reader = XmlReader.Create(stream, new XmlReaderSettings() {
DtdProcessing = DtdProcessing.Ignore,
IgnoreWhitespace = (loadOptions&LoadOptions.PreserveWhitespace) == LoadOptions.None,
XmlResolver = null,
CloseInput = false,
Async = true
})) {
var result = new XDocument();
using (var writer = result.CreateWriter()) {
do {
switch (reader.NodeType) {
case XmlNodeType.Element:
writer.WriteStartElement(reader.Prefix, reader.LocalName, reader.NamespaceURI);
writer.WriteAttributes(reader, true);
if (reader.IsEmptyElement) {
writer.WriteEndElement();
}
break;
case XmlNodeType.Text:
writer.WriteString(await reader.GetValueAsync().ConfigureAwait(false));
break;
case XmlNodeType.CDATA:
writer.WriteCData(reader.Value);
break;
case XmlNodeType.EntityReference:
writer.WriteEntityRef(reader.Name);
break;
case XmlNodeType.ProcessingInstruction:
case XmlNodeType.XmlDeclaration:
writer.WriteProcessingInstruction(reader.Name, reader.Value);
break;
case XmlNodeType.Comment:
writer.WriteComment(reader.Value);
break;
case XmlNodeType.DocumentType:
writer.WriteDocType(reader.Name, reader.GetAttribute("PUBLIC"), reader.GetAttribute("SYSTEM"), reader.Value);
break;
case XmlNodeType.Whitespace:
case XmlNodeType.SignificantWhitespace:
writer.WriteWhitespace(await reader.GetValueAsync().ConfigureAwait(false));
break;
case XmlNodeType.EndElement:
writer.WriteFullEndElement();
break;
}
} while (await reader.ReadAsync().ConfigureAwait(false));
}
return result;
}
}发布于 2017-04-25 07:35:26
首先,任务不是异步运行的。您需要使用内置的异步IO命令,或者自己在线程池上拆分一个任务。例如
public static Task<XDocument> LoadAsync
( String path
, LoadOptions loadOptions = LoadOptions.PreserveWhitespace
)
{
return Task.Run(()=>{
using (var stream = File.OpenText(path))
{
return XDocument.Load(stream, loadOptions);
}
});
}如果使用Parse的流版本,则不会得到临时字符串。
https://stackoverflow.com/questions/43590338
复制相似问题