简体   繁体   English

使用待处理的异步读取处理XmlReader

[英]Disposing of XmlReader with pending async read

I'm writing a .NET XMPP library for fun and as has been discussed elsewhere the XmlReader implementation in versions prior to .NET 4.5 was not suitable for parsing XML from a NetworkStream as it would not begin parsing until it filled an internal 4KB buffer or reached EOF. 我正在编写一个.NET XMPP库,这很有趣,而且在其他地方已经讨论过XmlReader之前的版本中的XmlReader实现不适合从NetworkStream解析XML,因为直到填充内部4KB缓冲区或达到了EOF。

Other libraries got around this by not using XmlReader at all. 其他库通过完全不使用XmlReader解决此问题。 As mentioned in the previously linked question, jabber-net uses a port of a Java XML parser. 如先前链接的问题所述,jabber-net使用Java XML解析器的端口。 An implementation I found while searching, Babel IM, uses its own simple XML parser . 我在搜索时发现的实现Babel IM使用了自己的简单XML解析器 I'm not sure what agsXMPP does. 我不确定agsXMPP会做什么。

However, with the release of .NET 4.5 and the new async features XmlReader apparently got an upgrade and can now do true async parsing . 但是,随着.NET 4.5的发布和新的异步功能, XmlReader显然得到了升级,现在可以进行真正的异步解析 I've thus used it to hack together a fairly simple XMPP client that can connect to a server and send and receive messages. 因此,我用它来破解一个相当简单的XMPP客户端,该客户端可以连接到服务器并发送和接收消息。

The sticking point however, actually seems to be in disconnecting from the server. 然而,症结实际上似乎在于与服务器断开连接 On disconnect I would normally just want to Dispose() of my XmlReader instance and the underlying streams. 在断开连接时,我通常只想对XmlReader实例和基础流进行Dispose() However, Dispose() will actually throw an InvalidOperationException with the message "An asynchronous operation is already in progress." 但是, Dispose()实际上会引发InvalidOperationException ,并显示消息“异步操作已在进行中”。 if you call it when an async... well what the message says. 如果您在异步时调用它...消息中的内容。 However, because of the nature of XMPP, my XmlReader is basically constantly performing an async operation as it waits for XML stanzas from the server to come down the pipe. 但是,由于XMPP的性质,我的XmlReader在等待来自服务器的XML节通过管道时,基本上一直在执行异步操作。

There do not appear to be any methods on the XmlReader that I could use to tell it to cancel any pending async operations so that I can Dispose() of it cleanly. XmlReader上似乎没有任何方法可以用来告诉它取消任何暂挂的异步操作,以便可以干净地Dispose() Is there a better way to deal with this situation than simply not attempting to dispose of the XmlReader ? 是否有比不尝试处置XmlReader更好的方法来处理这种情况? The XMPP spec states that the server is supposed to send a closing </stream:stream> tag on disconnect. XMPP 规范指出,服务器应该在断开连接时发送关闭</stream:stream>标记。 I could use this as a signal to not attempt to perform another async read as nothing else should be coming down the pipe, but there's no guarantee of this. 可以将其用作不尝试执行另一次异步读取的信号,因为没有其他东西可以通过管道传输了,但是并不能保证这样做。

Here is some sample code to play with. 这是一些示例代码。 LongLivedTextStream basically emulates an open NetworkStream in that it never reaches EOF and will block until at least 1 byte can be read. LongLivedTextStream基本上模拟一个开放的NetworkStream ,因为它永远不会到达EOF,并且将阻塞直到至少可以读取1个字节为止。 You can "inject" XML text into it which the XmlReader will happily parse, but trying to dispose of the reader will trigger the aforementioned exception. 您可以将XML文本“注入” XmlReader将很高兴地对其进行解析,但是尝试处置该阅读器将触发上述异常。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;

namespace Example
{
    class LongLivedTextStream : Stream
    {
        ManualResetEvent moarDatas = new ManualResetEvent(false);

        List<byte> data = new List<byte>();
        int pos = 0;

        public void Inject(string text)
        {
            data.AddRange(new UTF8Encoding(false).GetBytes(text));

            moarDatas.Set();
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            var bytes = GetBytes(count).ToArray();

            for (int i = 0; offset + i < buffer.Length && i < bytes.Length; i++)
            {
                buffer[offset + i] = bytes[i];
            }

            return bytes.Length;
        }

        private IEnumerable<byte> GetBytes(int count)
        {
            int returned = 0;

            while (returned == 0)
            {
                if (pos < data.Count)
                {
                    while (pos < data.Count && returned < count)
                    {
                        yield return data[pos];

                        pos += 1; returned += 1;
                    }
                }
                else
                {
                    moarDatas.Reset();
                    moarDatas.WaitOne();
                }
            }
        }

        #region Other Stream Members

        public override bool CanRead
        {
            get { return true; }
        }

        public override bool CanSeek
        {
            get { return false; }
        }

        public override bool CanWrite
        {
            get { return false; }
        }

        public override void Flush() { }

        public override long Length
        {
            get { throw new NotSupportedException(); }
        }

        public override long Position
        {
            get { throw new NotSupportedException(); }
            set { throw new NotSupportedException(); }
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotSupportedException();
        }

        public override void SetLength(long value)
        {
            throw new NotSupportedException();
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            throw new NotSupportedException();
        }

        #endregion
    }

    public class Program
    {
        public static void Main(string[] args)
        {
            Test();
            Console.ReadLine();
        }

        public static async void Test()
        {
            var stream = new LongLivedTextStream();
            var reader = XmlReader.Create(stream, new XmlReaderSettings() { Async = true });

            var t = Task.Run(() =>
                {
                    stream.Inject("<root>");
                    Thread.Sleep(2000);
                    stream.Inject("value");
                    Thread.Sleep(2000);
                    stream.Inject("</root>");
                    Thread.Sleep(2000);

                    reader.Dispose(); // InvalidOperationException: "An asynchronous operation is already in progress."

                    Console.WriteLine("Disposed");
                });

            while (await reader.ReadAsync())
            {
                bool kill = false;

                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        Console.WriteLine("Start: " + reader.LocalName);
                        break;

                    case XmlNodeType.EndElement:
                        Console.WriteLine("End:   " + reader.LocalName);
                        //kill = true; // I could use a particular EndElement as a signal to not try another read
                        break;

                    case XmlNodeType.Text:
                        Console.WriteLine("Text:  " + await reader.GetValueAsync());
                        break;
                }

                if (kill) { break; }
            }
        }
    }
}

EDIT 编辑

This example uses an actual NetworkStream and shows that if I Close() or Dispose() of the underlying stream the ReadAsync() call on XmlReader does not return false as hoped, instead it continues to block. 本示例使用实际的NetworkStream并显示,如果我对基础流进行Close()Dispose() ,则XmlReader上的ReadAsync()调用不会返回希望的false,而是继续阻塞。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;

namespace Example
{
    public class Program
    {
        public static void Main(string[] args)
        {
            NetworkStream stream = null;

            var endpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 50000);                                   

            var serverIsUp = new ManualResetEvent(false);
            var doneWriting = new ManualResetEvent(false);

            var t1 = Task.Run(() =>
            {
                var server = new TcpListener(endpoint);
                server.Start();

                serverIsUp.Set();

                var client = server.AcceptTcpClient();

                var writer = new StreamWriter(client.GetStream());

                writer.Write("<root>"); writer.Flush();
                Thread.Sleep(2000);
                writer.Write("value"); writer.Flush();
                Thread.Sleep(2000);
                writer.Write("</root>"); writer.Flush();
                Thread.Sleep(2000);

                doneWriting.Set();
            });

            var t2 = Task.Run(() =>
                {
                    doneWriting.WaitOne();

                    stream.Dispose();

                    Console.WriteLine("Disposed of Stream");
                });

            var t3 = Task.Run(async () =>
            {
                serverIsUp.WaitOne();                

                var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
                socket.Connect(endpoint);

                stream = new NetworkStream(socket, true);

                var reader = XmlReader.Create(stream, new XmlReaderSettings() { Async = true });

                bool val;
                while (val = await reader.ReadAsync())
                {
                    bool kill = false;

                    switch (reader.NodeType)
                    {
                        case XmlNodeType.Element:
                            Console.WriteLine("Start: " + reader.LocalName);
                            break;

                        case XmlNodeType.EndElement:
                            Console.WriteLine("End:   " + reader.LocalName);
                            //kill = true; // I could use a particular EndElement as a signal to not try another read
                            break;

                        case XmlNodeType.Text:
                            Console.WriteLine("Text:  " + await reader.GetValueAsync());
                            break;
                    }

                    if (kill) { break; }
                }

                // Ideally once the underlying stream is closed, ReadAsync() would return false
                // we would get here and could safely dispose the reader, but that's not the case
                // ReadAsync() continues to block
                reader.Dispose();
                Console.WriteLine("Disposed of Reader");
            });

            Console.ReadLine();
        }
    }
}

Try injecting a manual </stream:stream> into the parser. 尝试将手动</stream:stream>注入解析器。 To do this, you may need an adapter class between the NetworkStream and the parser, which passes all of the incoming data to the parser but adds another method to inject the </stream:stream> . 为此,您可能需要在NetworkStream和解析器之间使用适配器类,该适配器类将所有传入数据传递到解析器,但添加了另一种方法来注入</stream:stream> You'll need to be careful that you're not in the middle of another stanza when you call that method, perhaps by keeping state on the output side of the parser. 您需要注意,在调用该方法时,请不要将其置于另一个节的中间,方法可能是将状态保留在解析器的输出端。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM