异步加载 XDocument
Load XDocument asynchronously
我想将大型 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
,而无需创建大型临时字符串?
首先,任务不是 运行 异步执行的。您需要使用内置的异步 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 的 stream version,则您不会获得临时字符串。
XDocument.LoadAsync()
在 .NET Core 2.0 中可用:https://docs.microsoft.com/en-us/dotnet/api/system.xml.linq.xdocument.loadasync?view=netcore-2.0
迟到的答案,但我也需要在“遗留”.NET Framework 版本上进行异步读取,所以我想出了一种真正以异步方式读取内容的方法,而无需恢复为缓冲 XML内存中的数据。
由于 XDocument.CreateWriter()
提供的写入器不支持异步写入,因此 XmlWriter.WriteNodeAsync()
失败,代码执行异步读取并将其转换为 XDocument 写入器上的同步写入。然而,该代码的灵感来自于 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;
}
}
我想将大型 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
,而无需创建大型临时字符串?
首先,任务不是 运行 异步执行的。您需要使用内置的异步 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 的 stream version,则您不会获得临时字符串。
XDocument.LoadAsync()
在 .NET Core 2.0 中可用:https://docs.microsoft.com/en-us/dotnet/api/system.xml.linq.xdocument.loadasync?view=netcore-2.0
迟到的答案,但我也需要在“遗留”.NET Framework 版本上进行异步读取,所以我想出了一种真正以异步方式读取内容的方法,而无需恢复为缓冲 XML内存中的数据。
由于 XDocument.CreateWriter()
提供的写入器不支持异步写入,因此 XmlWriter.WriteNodeAsync()
失败,代码执行异步读取并将其转换为 XDocument 写入器上的同步写入。然而,该代码的灵感来自于 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;
}
}