[英]Disposing underlying MemoryStream of an XmlReader returned by a method
[英]Disposing of XmlReader with pending async read
我正在編寫一個.NET XMPP庫,這很有趣,而且在其他地方已經討論過XmlReader
之前的版本中的XmlReader
實現不適合從NetworkStream
解析XML,因為直到填充內部4KB緩沖區或達到了EOF。
其他庫通過完全不使用XmlReader
解決此問題。 如先前鏈接的問題所述,jabber-net使用Java XML解析器的端口。 我在搜索時發現的實現Babel IM使用了自己的簡單XML解析器 。 我不確定agsXMPP會做什么。
但是,隨着.NET 4.5的發布和新的異步功能, XmlReader
顯然得到了升級,現在可以進行真正的異步解析 。 因此,我用它來破解一個相當簡單的XMPP客戶端,該客戶端可以連接到服務器並發送和接收消息。
然而,症結實際上似乎在於與服務器斷開連接 。 在斷開連接時,我通常只想對XmlReader
實例和基礎流進行Dispose()
。 但是, Dispose()
實際上會引發InvalidOperationException
,並顯示消息“異步操作已在進行中”。 如果您在異步時調用它...消息中的內容。 但是,由於XMPP的性質,我的XmlReader
在等待來自服務器的XML節通過管道時,基本上一直在執行異步操作。
在XmlReader
上似乎沒有任何方法可以用來告訴它取消任何暫掛的異步操作,以便可以干凈地Dispose()
。 是否有比不嘗試處置XmlReader
更好的方法來處理這種情況? XMPP 規范指出,服務器應該在斷開連接時發送關閉</stream:stream>
標記。 我可以將其用作不嘗試執行另一次異步讀取的信號,因為沒有其他東西可以通過管道傳輸了,但是並不能保證這樣做。
這是一些示例代碼。 LongLivedTextStream
基本上模擬一個開放的NetworkStream
,因為它永遠不會到達EOF,並且將阻塞直到至少可以讀取1個字節為止。 您可以將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; }
}
}
}
}
編輯
本示例使用實際的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();
}
}
}
嘗試將手動</stream:stream>
注入解析器。 為此,您可能需要在NetworkStream和解析器之間使用適配器類,該適配器類將所有傳入數據傳遞到解析器,但添加了另一種方法來注入</stream:stream>
。 您需要注意,在調用該方法時,請不要將其置於另一個節的中間,方法可能是將狀態保留在解析器的輸出端。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.