如何用RX实现超时序列?

时间:2017-11-15 12:59:12

标签: c# system.reactive .net-4.6

方案如下:如果在短时间内向服务器回调,则认为通信的设备是连接的。 我想创建一个类,它封装了跟踪这种状态的功能。在调用设备时,应重置超时。在回调时,连接已确认,状态应设置为 true ,如果回调超时,则应将其设置为 false 。但是下一次调用应该能够再次无效地重置超时当前状态。

我正在考虑使用swithtimeout来实现这一点。但我不知道为什么它会停止工作。

public class ConnectionStatus
{
private Subject<bool> pending = new Subject<bool>();
private Subject<bool> connected = new Subject<bool>();

public bool IsConnected { get; private set; }

public ConnectionStatus(CancellationToken token, short timeoutSeconds = 15)
{
    pending.Select(outer => connected.Timeout(TimeSpan.FromSeconds(timeoutSeconds))) 
        .Switch()
        .Subscribe(_ => IsConnected = true, e => IsConnected = false, token);
}

public void ConfirmConnected()
{
    connected.OnNext(true);
}

public void SetPending()
{
    pending.OnNext(true);
}
}

这是&#34;测试用例&#34;:

var c = new ConnectionStatus(default(CancellationToken));

c.SetPending();
await Task.Delay(TimeSpan.FromSeconds(5));
c.ConfirmConnected();   
c.IsConnected.Dump(); // TRUE, OK

c.SetPending();
await Task.Delay(TimeSpan.FromSeconds(5));
c.ConfirmConnected();
c.IsConnected.Dump(); // TRUE, OK

c.SetPending();
await Task.Delay(TimeSpan.FromSeconds(20));
c.IsConnected.Dump(); // FALSE, OK
c.ConfirmConnected(); 
c.IsConnected.Dump(); // FALSE, OK

c.SetPending();
await Task.Delay(TimeSpan.FromSeconds(10));
c.ConfirmConnected(); 
c.IsConnected.Dump(); // FALSE, NOT OK!

我假设内部observable的超时也停止了外部的observable。因为outer => lambda不再被调用。什么是正确的方法?

谢谢

2 个答案:

答案 0 :(得分:3)

问题是Timeout本质上导致异常炸毁Rx订阅。触发超时后(因为它已编码),将不会发送其他通知。 Rx语法是,您可以有* OnNext条消息,后跟一个OnCompleted或一个OnError。在OnError发送Timeout后,您将不会再看到任何消息。

您需要通过OnNext消息而不是OnError消息传递超时消息。在旧代码中,您将任何OnError变为false,将任何OnNext变为true。相反,您需要将适当的新IsConnected值嵌入OnNext条消息中。以下是如何做到这一点:

public ConnectionStatus(CancellationToken token, short timeoutSeconds = 15)
{
    pending.Select(_ => connected
            .Timeout(TimeSpan.FromSeconds(timeoutSeconds))
            .Materialize()
            .Select(n => n.Kind == NotificationKind.OnError && n.Exception.GetType() == typeof(TimeoutException) 
                ? Notification.CreateOnNext(false)
                : n)
            .Dematerialize()
            .Take(1)
        )
        .Switch()
        .Subscribe(b => IsConnected = b, token);
}

答案 1 :(得分:3)

以下是在不使用IsConnected的情况下生成.TimeOut值流的替代方法:

public class ConnectionStatus
{
    private Subject<Unit> pending = new Subject<Unit>();
    private Subject<Unit> connected = new Subject<Unit>();

    public bool IsConnected { get; private set; }

    public ConnectionStatus(CancellationToken token, short timeoutSeconds = 15)
    {
        pending
            .Select(outer =>
                Observable.Amb(
                    connected.Select(_ => true),
                    Observable.Timer(TimeSpan.FromSeconds(timeoutSeconds)).Select(_ => false)))
            .Switch()
            .Subscribe(isConnected => IsConnected = isConnected, token);
    }

    public void ConfirmConnected()
    {
        connected.OnNext(Unit.Default);
    }

    public void SetPending()
    {
        pending.OnNext(Unit.Default);
    }
}

Observable.Amb运算符只是从任何一个observable产生一个值得到一个值 - 它最好用异常编码。