C# 如何通过RX实现超时顺序?

C# 如何通过RX实现超时顺序?,c#,system.reactive,.net-4.6,C#,System.reactive,.net 4.6,该场景如下:如果通过网络进行通信的设备在短时间内回调服务器,则认为该设备已连接。 我想创建一个类来封装跟踪此状态的功能。调用设备时,应重置超时。在回调时,连接被确认,状态应设置为true,如果回调超时,则应设置为false。但是下一次调用应该能够重新设置超时,而与当前状态无关 我想通过使用swith和timeout来实现这一点。但我不知道为什么它停止工作 public class ConnectionStatus { private Subject<bool> pending = n

该场景如下:如果通过网络进行通信的设备在短时间内回调服务器,则认为该设备已连接。 我想创建一个类来封装跟踪此状态的功能。调用设备时,应重置超时。在回调时,连接被确认,状态应设置为true,如果回调超时,则应设置为false。但是下一次调用应该能够重新设置超时,而与当前状态无关

我想通过使用
swith
timeout
来实现这一点。但我不知道为什么它停止工作

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);
}
}
我假设内部可观测的超时也停止了外部可观测的超时。因为不再调用
outer=>
lambda。正确的方法是什么


谢谢

问题在于,
超时
实质上会导致异常导致接收订阅中断。触发超时后(按照您的编码),将不会发送其他通知。Rx语法是,您可以在*
OnNext
消息后加一个
OnCompleted
或一个
OnError
。发送
Timeout
中的
OnError
后,您将看不到更多消息

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

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);
}

这里有一种替代方法,可以在不使用
.TimeOut
的情况下生成
IsConnected
值流:

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);
    }
}
公共类连接状态
{
私有主题待定=新主题();
连接的私有主题=新主题();
公共布尔已连接{get;private set;}
公共连接状态(CancellationToken令牌,短时超时=15)
{
悬而未决的
.选择(外部=>
可观察的(
已连接。选择(=>true),
可观察的.Timer(TimeSpan.FromSeconds(timeoutSeconds))。选择(=>false)))
.Switch()
.Subscribe(isConnected=>isConnected=isConnected,令牌);
}
公共无效确认连接()
{
connected.OnNext(单位默认值);
}
public void SetPending()
{
未决.OnNext(单位默认值);
}
}

Observable.Amb
操作符只需从哪个Observable首先生成一个值的值中获取一个值-这比编码异常要好。

谢谢。我已经测试了您的建议,发现它需要一个
.FirstOrDefaultAsync()
选择之后。如果没有它,即使
已连接
输入了该值,也会触发超时。同时,我找到了另一个解决方案:
pending.Select(=>connected.Buffer(TimeSpan.FromSeconds(timeoutSeconds),1.FirstOrDefaultAsync()).Switch().Subscribe(l=>IsConnected=l.Count>0,令牌)
在什么情况下您发现需要
FirstOrDefaultAsync
c.SetPending();等待任务延迟(时间跨度从秒(5));c.确认连接();c.IsConnected.Dump();等待任务延迟(时间跨度从秒(20));c.IsConnected.Dump()
后一个应该仍然为TRUE,但如果没有
FirstOrDefaultAsync
,则为FALSE。修改了答案以修复该错误,以及另一个可能的错误(不会传播异常)。你的替补队员很好。
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);
    }
}