异步加载XDocument

时间:2017-04-24 14:08:32

标签: c# asynchronous async-await linq-to-xml

我想将大型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()能够流式传输源文件,并且永远不需要创建一个巨大的String来保存整个文件。

有没有办法让两全其美?使用完全异步I / O加载XDocument,而无需创建大型临时字符串?

3 个答案:

答案 0 :(得分:5)

答案 1 :(得分:3)

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

.
|-- pom.xml
`-- src
    |-- it
    |   |-- resources
    |   |   `-- data.csv
    |   `-- scala  #### TEST SOURCES ###
    |       `-- com
    |           `-- my
    |               `-- example
    |                   `-- jetty
    |                       `-- JettyBasedServerIT.scala
    |-- main
    |   `-- scala #### SOURCES ###
    |       `-- com
    |           `-- my
    |               `-- example
    |                   `-- jetty
    |                       `-- JettyBasedServer.scala
    `-- test
        `-- scala #### TEST SOURCES ###
            `-- com
                `-- my
                    `-- example
                        `-- jetty
                            `-- MainArgumentsTest.scala

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

答案 2 :(得分:1)

迟到的答案,但我也需要在“传统”.NET Framework 版本上进行异步读取,因此我想出了一种方法,可以真正以异步方式读取内容,而无需恢复到在内存中缓冲 XML 数据。

由于 XDocument.CreateWriter() 提供的 writer 不支持异步写入,因此 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;
    }
}