.NET中的异步XmlReader?

7
有没有一种方法可以异步访问XmlReader?XML是从许多不同的客户端,例如XMPP中通过网络传输的;它是一个不断流动的...标签序列。
我希望能够使用类似于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;
            };

1
你的计数器在这个情况下失败了,这是完全合法的XML:<foo bar='>baz'/>,其中读取边界在baz之前。 - Joe Hildebrand
6个回答

3

XmlReader在.NET 4.5中有大多数涉及IO的方法的异步版本。

请查看此处的示例代码。


2

最简单的方法就是将其放在另一个线程中,可能是线程池,具体取决于它保持活动状态的时间长短。(不要使用线程池线程来处理真正长时间运行的任务)。


它并没有。我并不是一定说每个客户端一个线程 :) - kyoryu
如果每个客户端在连接期间都有自己的 XML 流,那么你如何避免每个 XmlReader 都在自己的线程中运行? - KJ Tsanaktsidis
作业队列?这些流应该如何组合? - kyoryu

2
XmlReader以4kB的块缓冲,如果我记得几年前我研究这个问题时。你可以将传入数据填充到4kB(不好!),或者使用更好的解析器。我通过将James Clark的XP(Java)移植到C#作为Jabber-Net的一部分来解决了这个问题,链接在这里:http://code.google.com/p/jabber-net/source/browse/#svn/trunk/xpnet。它是LGPL协议,仅处理UTF8,未打包供使用,并且几乎没有文档,因此我不建议使用它。 :)

你能给我简单介绍一下如何使用这个解析器吗?多个实例是否可以异步地解析不同的套接字而无需自己的线程?(就像在XMPP中那样?) - KJ Tsanaktsidis
1
请参考以下链接的示例:http://code.google.com/p/jabber-net/source/browse/trunk/jabber/protocol/AsynchElementStream.cs创建一个UTF8Encoding,使用tokenizeContent或tokenizeCdataSection将字节传递给它,查看生成的令牌。字节来自何处以及确保您不会在不同线程上修改一个解析器状态的同步是由您决定的。如果您想要进行XMPP通信,则可以直接使用整个Jabber-Net库,这样可以避免一些麻烦。 - Joe Hildebrand
因此,似乎通用解决方案是找到一个XML解析器,它具有让我自己随意放置字节的接口,而不是提供流。解析器将在我提供内容时解析它,保留尚未解析的字节,因为它不是完整的XML节点。这听起来正确吗? - KJ Tsanaktsidis
此外,xpnet 似乎不是 LGPL 许可证;copying.txt 文件基本上表示您可以随心所欲地使用它。我有什么遗漏吗? - KJ Tsanaktsidis
对于你的第一个问题:是的。 Expat是这样一种解析器的很好的例子。对于第二个问题,意图是C#端口采用LGPL,但我包含了James Clark的copying.txt,因为该文本如下:“以上版权声明和此权限声明应包含在所有软件的副本或实质部分中。”该端口与原始XP代码非常接近,我想保持copying.txt文件以确保安全。 - Joe Hildebrand
Ahk。好的,LGPL对我来说没问题,所以我会尝试使用你的C#移植版expat。我想这就是我在寻找的答案:P 谢谢你! - KJ Tsanaktsidis

1
这个问题真的很棘手,因为XmlReader并没有提供任何异步接口。
我不太确定当你要求它读取0字节时,BeginRead到底能异步执行多少 - 可能会立即调用回调函数,然后在调用Read时阻塞。这可能与直接调用Read并在线程池中安排下一个Read(例如使用QueueWorkItem)是一样的。
最好使用网络流上的BeginRead来以10kB的块读取数据(当系统等待数据时,不会阻塞任何线程)。当你接收到一个块时,你可以将其复制到某个本地的MemoryStream中,然后你的XmlReader将从这个MemoryStream中读取数据。

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

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

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


我快速编写了一个测试,读取0字节的BeginRead是完全可以的,直到有数据准备好才会调用回调函数。现在我将尝试你的算法。 - KJ Tsanaktsidis
如果我知道消息长度,你所描述的问题就不会存在了,是吗? - KJ Tsanaktsidis
如果BeginRead使其等待至少一些数据,那么这可能是可以的(如果您正在下载小块)。如果您知道消息(一个<action>项)的长度,则可以读取恰好所需的字节数以执行下一个“Read”调用。但这仍然可能存在问题(例如,使用不同的文本编码等)。 - Tomas Petricek
是的,理想情况下我想要的是一种缓冲接收数据的方式,直到有足够的数据来读取下一个节点而不会阻塞。我会将我的想法作为问题的编辑发布... - KJ Tsanaktsidis

1

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


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

异步XAML加载操作将最初返回一个纯粹的根对象。然后,XAML解析会异步继续进行,并且任何子对象都将填充到根对象下面。


我认为XamlReader在新节点可用时不会触发事件,只有在完成加载标记时才会触发,而在我的情况下,这将是在连接关闭时。不过这确实是一种有趣的使用XAML的方式 :P - KJ Tsanaktsidis
想到了。不过我还是把我的答案留下来,以防后来有人需要它... - Rob Fonseca-Ensor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接