C#4.0(VS1010,XP)程序需要使用TCP连接到主机,发送和接收字节,有时会正确关闭连接并稍后重新打开。使用Rx.Net Observable
样式编写周围代码。数据量很小但程序应该连续运行(通过妥善处理资源来避免内存泄漏)。
下面的文字很长,因为我解释了我搜索和发现的内容。它现在似乎有效。
总体问题是:由于Rx有时不直观,解决方案是否良好?这是否可靠(比如说,它可能会运行多年没有问题)?
该程序获得NetworkStream
,如下所示:
TcpClient tcpClient = new TcpClient();
LingerOption lingerOption = new LingerOption(false, 0); // Make sure that on call to Close(), connection is closed immediately even if some data is pending.
tcpClient.LingerState = lingerOption;
tcpClient.Connect(remoteHostPort);
return tcpClient.GetStream();
异步发送很简单。与传统解决方案相比,Rx.Net允许使用更短更简洁的代码来处理此问题。我创建了一个EventLoopScheduler
的专用线程。需要发送的操作使用IObservable
表示。使用ObserveOn(sendRecvThreadScheduler)
保证所有发送操作都在该线程上完成。
sendRecvThreadScheduler = new EventLoopScheduler(
ts =>
{
var thread = new System.Threading.Thread(ts) { Name = "my send+receive thread", IsBackground = true };
return thread;
});
// Loop code for sending not shown (too long and off-topic).
到目前为止,这是优秀和完美的。
似乎要接收数据,Rx.Net还应该允许传统解决方案更短更清晰的代码。 在阅读了几个资源(例如http://www.introtorx.com/)和stackoverflow之后,似乎一个非常简单的解决方案是将异步编程桥接到Rx.Net,就像在https://stackoverflow.com/a/14464068/1429390中一样:
public static class Ext
{
public static IObservable<byte[]> ReadObservable(this Stream stream, int bufferSize)
{
// to hold read data
var buffer = new byte[bufferSize];
// Step 1: async signature => observable factory
var asyncRead = Observable.FromAsyncPattern<byte[], int, int, int>(
stream.BeginRead,
stream.EndRead);
return Observable.While(
// while there is data to be read
() => stream.CanRead,
// iteratively invoke the observable factory, which will
// "recreate" it such that it will start from the current
// stream position - hence "0" for offset
Observable.Defer(() => asyncRead(buffer, 0, bufferSize))
.Select(readBytes => buffer.Take(readBytes).ToArray()));
}
}
它主要起作用。我可以发送和接收字节。
这是事情开始出错的时候。
有时我需要关闭流并保持清洁。基本上这意味着:停止读取,结束字节接收observable,用新的连接打开新连接。
首先,当远程主机强制关闭连接时,BeginRead()/EndRead()
立即循环使用所有CPU返回零字节。我让更高级别的代码注意到这一点(在高级元素可用的上下文中使用Subscribe()
到ReadObservable
)并清理(包括关闭和处理流)。这也很有效,我负责处理Subscribe()
返回的对象。
someobject.readOneStreamObservableSubscription = myobject.readOneStreamObservable.Subscribe(buf =>
{
if (buf.Length == 0)
{
MyLoggerLog("Read explicitly returned zero bytes. Closing stream.");
this.pscDestroyIfAny();
}
});
有时,我只需要关闭流。但显然这必须导致在异步读取中抛出异常。 c# - Proper way to prematurely abort BeginRead and BeginWrite? - Stack Overflow
我添加了CancellationToken
,导致Observable.While()
结束序列。这对于避免这些异常没有多大帮助,因为BeginRead()
可以长时间睡眠。
observable中的未处理异常导致程序退出。正在搜索提供的.net - Continue using subscription after exception - Stack Overflow,其中建议添加Catch,以便有效地恢复已损坏的Observable
。
代码如下所示:
public static IObservable<byte[]> ReadObservable(this Stream stream, int bufferSize, CancellationToken token)
{
// to hold read data
var buffer = new byte[bufferSize];
// Step 1: async signature => observable factory
var asyncRead = Observable.FromAsyncPattern<byte[], int, int, int>(
stream.BeginRead,
stream.EndRead);
return Observable.While(
// while there is data to be read
() =>
{
return (!token.IsCancellationRequested) && stream.CanRead;
},
// iteratively invoke the observable factory, which will
// "recreate" it such that it will start from the current
// stream position - hence "0" for offset
Observable.Defer(() =>
{
if ((!token.IsCancellationRequested) && stream.CanRead)
{
return asyncRead(buffer, 0, bufferSize);
}
else
{
return Observable.Empty<int>();
}
})
.Catch(Observable.Empty<int>()) // When BeginRead() or EndRead() causes an exception, don't choke but just end the Observable.
.Select(readBytes => buffer.Take(readBytes).ToArray()));
}
这似乎运作良好。检测到远程主机强制关闭连接或者不再可访问的条件,导致更高级别的代码关闭连接并重试。到目前为止一切都很好。
我不确定事情是否正确。
首先,那一行:
.Catch(Observable.Empty<int>()) // When BeginRead() or EndRead() causes an exception, don't choke but just end the Observable.
感觉就像命令式代码中空捕获块的坏习惯。实际代码会记录异常,而更高级别的代码会检测到没有回复并正确处理,所以它应该被认为是相当正常的(见下文)?
.Catch((Func<Exception, IObservable<int>>)(ex =>
{
MyLoggerLogException("On asynchronous read from network.", ex);
return Observable.Empty<int>();
})) // When BeginRead() or EndRead() causes an exception, don't choke but just end the Observable.
此外,这确实比大多数传统解决方案更短。
解决方案是正确的还是我错过了一些更简单/更清洁的方法?
对于Reactive Extensions的向导,是否存在一些显而易见的可怕问题?
感谢您的关注。