C# 通过FromAsyncPattern使用Observable读取流,如何正确关闭/取消 需要:具有TCP连接的长时间运行程序

C# 通过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

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();
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.