异步加载 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;
    }
}