XmlWriter 异步操作失败 XmlWriterSettings.OutputMethod = Html

XmlWriter Async operations fail with XmlWriterSettings.OutputMethod = Html

使用 XmlWriterSettings.OutputMethod = OutputMethod.Html 创建 XmlWriter 时,异步操作失败。使用 OutputMethod.AutoDetect(默认)创建相同内容时,异步操作成功。

失败代码(fiddle):

var transform = new XslCompiledTransform();
using var reader = XmlReader.Create(new StringReader(@"
  <xsl:stylesheet version=""1.0"" xmlns:xsl=""http://www.w3.org/1999/XSL/Transform"">
    <xsl:output method=""html"" indent=""yes"" doctype-system=""html""/>
    <xsl:template match=""/"">
      <bar/>
    </xsl:template>
  </xsl:stylesheet>"));
transform.Load(reader);

var settings = transform.OutputSettings.Clone();
settings.CloseOutput = false;
settings.Async = true;

using var stream = new MemoryStream();
using (var writer = XmlWriter.Create(stream, settings))
{
    await writer.WriteStartDocumentAsync();
    await writer.WriteStartElementAsync(null, "foo", null);
    await writer.WriteEndElementAsync();
    await writer.WriteEndDocumentAsync();
}
stream.Position = 0;
var content = new StreamReader(stream).ReadToEnd();
Assert.Contains("foo", content);

使用堆栈跟踪:

Message: 
System.NotImplementedException : The method or operation is not implemented.

  Stack Trace: 
XmlWriter.WriteStartElementAsync(String prefix, String localName, String ns)
XmlWellFormedWriter.WriteStartElementAsync_NoAdvanceState(String prefix, String localName, String ns)
XmlWellFormedWriter.WriteStartElementAsync(String prefix, String localName, String ns)
XmlAsyncCheckWriter.WriteStartElementAsync(String prefix, String localName, String ns)

工作代码(工作 fiddle):

var settings = new XmlWriterSettings();
settings.CloseOutput = false;
settings.Async = true;

using var stream = new MemoryStream();
using (var writer = XmlWriter.Create(stream, settings))
{
    await writer.WriteStartDocumentAsync();
    await writer.WriteStartElementAsync(null, "foo", null);
    await writer.WriteEndElementAsync();
    await writer.WriteEndDocumentAsync();
}
stream.Position = 0;
var content = new StreamReader(stream).ReadToEnd();
Assert.Contains("foo", content);

在调试模式下检查各种东西,两个代码路径似乎都在幕后使用 System.Xml.XmlAsyncCheckWriter

有趣的是,这不是 OutputMethod 造成的,而是 doctype-system (编辑:抱歉,这实际上是两者的结合,您将在下面看到)。删除属性,您的异步调用将神奇地工作。

我可以告诉你发生了什么,但不能告诉你为什么他们选择这样做。


首先,作者是XmlWriterSettings.CreateWriter(Stream)创作的。去掉所有的绒毛,它是这样的:

internal XmlWriter CreateWriter(Stream output)
{
    XmlWriter writer;
    if (Encoding.WebName == "utf-8") {
        switch (OutputMethod) {
            case XmlOutputMethod.Html:
                writer= new HtmlUtf8RawTextWriter(output, this);
                break;
        }
    }

    // Wrap with Xslt/XQuery specific writer if needed;
    // XmlOutputMethod.AutoDetect writer does this lazily when it creates the underlying Xml or Html writer.
    if (OutputMethod != XmlOutputMethod.AutoDetect) {
        if (IsQuerySpecific) {
            // Create QueryOutputWriter if CData sections or DocType need to be tracked
            writer = new QueryOutputWriter((XmlRawWriter)writer, this);
        }
    }

    // wrap with well-formed writer
    writer = new XmlWellFormedWriter(writer, this);

    if (_useAsync)
        writer = new XmlAsyncCheckWriter(writer);

    return writer;
}

所以最后,你得到 onion/ogre 层

XmlAsyncCheckWriter(
    XmlWellFormedWriter(
        QueryOutputWriter(
            HtmlUtf8RawTextWriter)))

当你进行 Write...Async() 调用时,你会期望它从外部 Writer a 一直向下级联到 HtmlUtf8RawTextWriter 中的最深层次 - 其中确实有 the async calls你要

不幸的是,QueryOutputWriter 包装器不会将异步调用委托给内部编写器,实际上是抛出 ​​NotImplementedException. 的包装器是吗错误?还是刻意的选择?我不知道。

如果您不需要 DOCTYPE,并且不在输出中使用 CDATA(均由我们有问题的 QueryOutputWriter 处理),只需从 XSL 中删除 doctype-system 即可解决您的问题问题。这将导致以下 IsQuerySpecific 成为 false,从而防止不希望的换行。

private bool IsQuerySpecific => 
    CDataSectionElements.Count != 0
    || _docTypePublic != null
    || _docTypeSystem != null
    || _standalone == XmlStandalone.Yes;

...

if (IsQuerySpecific)
    xmlWriter = new QueryOutputWriter((XmlRawWriter)xmlWriter, this);

如果您确实需要 DOCTYPE/CDATA,那么重新实现一些层并覆盖函数将是一个有趣的练习。