【问题标题】:Asynchronous XmlReader in .NET?.NET 中的异步 XmlReader?
【发布时间】:2011-01-16 20:34:13
【问题描述】:

有没有办法异步访问 XmlReader? xml 是从许多不同的客户端(如 XMPP)从网络中传入的;它是<action>...</action> 标签的恒定流。

我追求的是能够使用类似 BeginRead/EndRead 的界面。我设法想出的最佳解决方案是在底层网络流上对 0 字节进行异步读取,然后当一些数据到达时,在 XmlReader 上调用 Read - 但是这将阻塞,直到节点中的所有数据变得可用。该解决方案大致如下所示

private Stream syncstream;
private NetworkStream ns;
private XmlReader reader;

//this code runs first
public void Init()
{
    syncstream = Stream.Synchronized(ns);
    reader = XmlReader.Create(syncstream);
    byte[] x = new byte[1];
    syncstream.BeginRead(x, 0, 0, new AsynchronousCallback(ReadCallback), null);
}

private void ReadCallback(IAsyncResult ar)
{
    syncstream.EndRead(ar);
    reader.Read(); //this will block for a while, until the entire node is available
    //do soemthing to the xml node
    byte[] x = new byte[1];
    syncstream.BeginRead(x, 0, 0, new AsynchronousCallback(ReadCallback), null);
}

编辑:如果字符串包含完整的 xml 节点,这是一种可能的算法吗?

Func<string, bool> nodeChecker = currentBuffer =>
                {
                    //if there is nothing, definetly no tag
                    if (currentBuffer == "") return false;
                    //if we have <![CDATA[ and not ]]>, hold on, else pass it on
                    if (currentBuffer.Contains("<![CDATA[") && !currentBuffer.Contains("]]>")) return false;
                    if (currentBuffer.Contains("<![CDATA[") && currentBuffer.Contains("]]>")) return true;
                    //these tag-related things will also catch <? ?> processing instructions
                    //if there is a < but no >, we still have an open tag
                    if (currentBuffer.Contains("<") && !currentBuffer.Contains(">")) return false;
                //if there is a <...>, we have a complete element.
                //>...< will never happen because we will pass it on to the parser when we get to >
                if (currentBuffer.Contains("<") && currentBuffer.Contains(">")) return true;
                //if there is no < >, we have a complete text node
                if (!currentBuffer.Contains("<") && !currentBuffer.Contains(">")) return true;
                //> and no < will never happen, we will pass it on to the parser when we get to >
                //by default, don't block
                return false;
            };

【问题讨论】:

  • 您的计数器在这种情况下失败,这是完全合法的 XML:,其中读取边界在 baz 之前。

标签: c# xml xmpp


【解决方案1】:

XmlReader 缓冲区以 4kB 块为单位,如果我记得几年前我研究过这个的话。您可以将入站数据填充到 4kB(ick!),或者使用更好的解析器。我通过将 James Clark 的 XP (Java) 作为 Jabber-Net 的一部分移植到 C# 来解决这个问题,这里:

http://code.google.com/p/jabber-net/source/browse/#svn/trunk/xpnet

它是 LGPL,只处理 UTF8,没有打包使用,几乎没有文档,所以我不建议使用它。 :)

【讨论】:

  • 你能给我简要介绍一下如何使用这个解析器吗?多个实例会在不需要自己的线程的情况下异步解析不同的套接字吗? (比如在 xmpp 中?)
  • 参见:code.google.com/p/jabber-net/source/browse/trunk/jabber/… 示例。创建一个 UTF8Encoding,用 tokenizeContent 或 tokenizeCdataSection 向它抛出字节,查看出来的令牌。字节来自哪里,以及确保您不会在不同线程上修改一个解析器的状态的同步取决于您。如果你想做 XMPP,你可以使用所有的 Jabber-Net,省去一些麻烦。
  • 所以,general 的解决方案似乎是找到一个带有接口的 xml 解析器,让我可以在闲暇时自己将字节放入其中,而不是提供流。解析器将解析我提供的内容,保留由于它不是完整的 xml 节点而尚未解析的字节。听起来对吗?
  • 另外,xpnet 看起来不像是 LGPL; copying.txt 文件几乎说你可以用它做任何你喜欢的事情。我错过了什么吗?
  • 第一个问题:是的。 Expat 是这种解析器的一个很好的例子。对于您的第二个,意图是 C# 端口是 LGPL,但由于以下文本,我包括了 James Clark 的 copying.txt:“上述版权声明和本许可声明应包含在软件的所有副本或大部分内容中。”该端口与原始 XP 代码足够接近,我认为我会保留 copying.txt 文件以确保安全。
【解决方案2】:

最简单的做法就是将它放在另一个线程上,也许是一个 ThreadPool,具体取决于它保持活动的时间长短。 (不要将线程池线程用于真正长时间运行的任务)。

【讨论】:

  • 它没有。我不一定说每个客户端一个线程:)
  • 因此,如果每个客户端在连接期间都有自己的 xml 流,您将如何避免将每个 XmlReader 都放在自己的线程中?
  • 工作队列?这些流应该如何组合?
【解决方案3】:
.NET 4.5 中的

XmlReader 具有大多数涉及 IO 的方法的 async 版本。

查看示例代码here

【讨论】:

    【解决方案4】:

    这真的很棘手,因为XmlReader 不提供任何异步接口。

    我不确定当您要求 BeginRead 读取 0 字节时它的异步行为有多少——它还不如立即调用回调,然后在您调用 Read 时阻塞。这可能与直接调用Read 然后在线程池中调度下一个Read 相同,例如使用QueueWorkItem

    最好在网络流上使用BeginRead 来读取例如10kB 块中的数据(当系统等待数据时,您不会阻塞任何线程)。当您收到一个块时,您会将其复制到某个本地 MemoryStream 并且您的 XmlReader 将从该 MemoryStream 读取数据。

    这仍然有一个问题 - 在复制 10kB 的数据并多次调用 Read 之后,最后一次调用会阻塞。然后您可能需要复制较小的数据块来解除对Read 的挂起调用的阻塞。完成后,您可以再次启动新的BeginRead 调用以异步读取大部分数据。

    老实说,这听起来很复杂,所以如果有人提出更好的答案,我很感兴趣。但是,它至少为您提供了一些有保证的异步操作,这些操作需要一些时间并且在此期间不会阻塞任何线程(这是异步编程的基本目标)。

    旁注:您可以尝试使用F# asynchronous workflows 来编写此代码,因为它们使异步代码更简单。我描述的技术即使在 F# 中也会很棘手)

    【讨论】:

    • 我进行了快速测试,BeginRead'ing 0 字节完全没问题,在某些数据准备好之前不会调用回调。我现在就试试你的算法
    • 另外,如果我知道消息长度,你描述的问题就不存在了,是吗?
    • 如果 BeginRead 让它至少等待一些数据,那么它可能没问题(如果你正在下载小块)。如果您知道消息(一个 项)的长度,那么您可以准确读取执行下一次Read 调用所需的字节数。但这可能仍然存在问题(例如,使用不同的文本编码等)
    • 是的,理想情况下,我想要的是某种方式来缓冲接收到的数据,直到有足够的时间读取下一个节点而不会阻塞。我将发布我的想法作为对我的问题的编辑......
    【解决方案5】:

    看起来 DOT NET 4.5 在 XmlReader 上有一个 bool Async 属性,而 3.5 中没有。也许这对你有用?

    【讨论】:

      【解决方案6】:

      您是否在寻找类似XamlReader.LoadAsync 方法的东西?

      异步 ​​XAML 加载操作 最初将返回一个对象 纯粹是根对象。 异步,然后 XAML 解析 继续,并且任何子对象都是 填在根下。

      【讨论】:

      • 我认为 XamlReader 不会在新节点可用时触发事件,只有在它完成加载标记时才会触发事件,在我的情况下,这将是在连接关闭时。将是一个有趣的 xaml 用途:P
      • 想了很多。留下我的答案,以防以后对其他人有帮助......
      猜你喜欢
      • 2012-08-14
      • 2011-10-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-26
      • 2012-02-19
      • 2023-03-05
      • 1970-01-01
      相关资源
      最近更新 更多