C# 通过FromAsyncPattern使用Observable读取流,如何正确关闭/取消 需要:具有TCP连接的长时间运行程序
C#4.0(VS1010,XP)程序需要使用TCP连接到主机,发送和接收字节,有时需要正确关闭连接,然后重新打开。周围的代码是使用Rx.NetC# 通过FromAsyncPattern使用Observable读取流,如何正确关闭/取消 需要:具有TCP连接的长时间运行程序,c#,.net,asynchronous,network-programming,rx.net,C#,.net,Asynchronous,Network Programming,Rx.net,C#4.0(VS1010,XP)程序需要使用TCP连接到主机,发送和接收字节,有时需要正确关闭连接,然后重新打开。周围的代码是使用Rx.NetObservable风格编写的。数据量较低,但程序应连续运行(通过妥善处理资源避免内存泄漏) 下面的文字很长,因为我解释了我搜索和发现的内容。它现在似乎起作用了 总体问题是:由于Rx有时不直观,解决方案好吗?这是否可靠(比如说,它可以运行多年而不出故障)? 迄今为止的解决办法 发送 程序将获得如下所示的NetworkStream: TcpClient tc
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();
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()));
}
异步发送非常简单。与传统的解决方案相比,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还应该允许比传统解决方案更短、更干净的代码。
在阅读了多个资源(例如)和stackoverflow之后,一个非常简单的解决方案似乎是将异步编程连接到Rx.Net,如:
有时候,我只需要关闭小溪。但显然,这必须导致在异步读取中抛出异常
我添加了一个CancellationToken
,它导致Observable.While()
结束序列。这对避免这些异常没有多大帮助,因为BeginRead()
可以长时间睡眠
observable中未处理的异常导致程序退出。提供的搜索建议添加一个有效地恢复损坏的可观察的
和一个空的
代码如下所示:
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();
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()));
}
publicstaticiobservable ReadObservable(此流,int-bufferSize,CancellationToken)
{
//保存读取的数据
var buffer=新字节[bufferSize];
//步骤1:异步签名=>可观察工厂
var asyncRead=Observable.FromAsyncPattern(
溪流开始,
stream.EndRead);
返回可观察。而(
//当有数据要读取时
() =>
{
返回(!token.IsCancellationRequested)&&stream.CanRead;
},
//迭代调用可观察工厂,它将
//“重新创建”它,使其从当前
//流位置-因此偏移量为“0”
可观察。延迟(()=>
{
if((!token.IsCancellationRequested)和&stream.CanRead)
{
返回asyncRead(缓冲区,0,缓冲区大小);
}
其他的
{
return-Observable.Empty();
}
})
.Catch(Observable.Empty())//当BeginRead()或EndRead()导致异常时,不要阻塞,只需结束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(Observable.Empty())//当BeginRead()或EndRead()导致异常时,不要阻塞,只需结束Observable。
感觉像是命令式代码中空catch块的糟糕做法。实际代码会记录异常,而更高级别的代码会检测到没有回复并正确处理,所以应该认为它还行(见下文)
.Catch((Func)(ex=>
{
MyLoggerLogException(“在从网络异步读取时”,例如);
return-Observable.Empty();
}))//当BeginRead()或EndRead()导致异常时,不要阻塞,只需结束可观察对象即可。
而且,这确实比大多数传统解决方案要短
解决方案是否正确,或者我是否错过了一些更简单/更干净的方法
是否存在一些对反应式扩展向导来说显而易见的可怕问题
感谢您的关注。您是否愿意放弃Rx并使用传统的等待循环来处理数据?谢谢。async/await来自VS2012/C#4.5,所以简短的回答是“否”。你能详细说明一下吗?async/await使本地并发代码具有双刃性。是否可以扩展到处理“随时从任何线程接受新命令+发送+发送之间的保证延迟+接收+检测丢失+重试+自动重新连接”逻辑?当前的Observable+事件循环方法有一条学习曲线,但看起来很明确,很有效。有陷阱吗?谢谢。您可以在.NET4.0和VS12中使用Wait。您可以在4.0和2010中使用基于迭代器的“假等待”。像“保证发送之间的延迟”这样的事情听起来确实有点像Rx,但一些简单的基于日期时间的逻辑应该能够做到这一点。重试和重新连接不是Wait会遇到的问题。从语义上讲,wait什么也不做。它只是简单地解开回调混乱,并将其转换为可读形式,您可以在其中使用常用的控制流构造。
.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.