Reactive Extensions超时不会停止序列?

时间:2011-11-09 11:06:59

标签: c# system.reactive

我正在尝试使IObservable<bool>返回true如果在最后5秒内收到UDP消息,并且如果发生超时,则返回false。

到目前为止,我有这个:

public IObservable<Boolean> GettingUDPMessages(IPEndPoint localEP)
{
    var udp = BaseComms.UDPBaseStringListener(localEP)
        .Where(msg => msg.Data.Contains("running"))
        .Select(s => true);

    return Observable
        .Timeout(udp, TimeSpan.FromSeconds(5))
        .Catch(Observable.Return(false));
}

问题是: -

  • 一旦返回false,序列就会停止
  • 我只需要truefalse进行状态更改。

我可以使用Subject<T>但是当没有更多订阅者时我需要小心处理UDPBaseStringListener observable。

更新

每当我收到UDP消息时,我希望它返回true。如果我在过去5秒内没有收到UDP消息,我希望它返回false

5 个答案:

答案 0 :(得分:3)

正如Bj0所指出的,带有BufferWithTime的解决方案在收到数据点后不会立即返回数据点,并且在收到数据点后未重置缓冲区超时。

使用Rx Extensions 2.0,您可以解决新缓冲区重载同时接受超时和大小的问题:

static IObservable<Boolean> GettingUDPMessages(IPEndPoint localEP)
{
    return BaseComms
        .UDPBaseStringListener(localEP)
        .Where(msg => msg.Data.Contains("running"))
        .Buffer(TimeSpan.FromSeconds(5), 1)
        .Select(s => s.Count > 0)
        .DistinctUntilChanged();
}

答案 1 :(得分:2)

缓冲区的问题是,当你获得一个新值时,“超时”间隔不会被重置,缓冲区窗口只是相互跟随的时间片段(在这种情况下是5秒)。这意味着,根据您收到最后一个值的时间,您可能需要等待几乎两倍的超时值。这也可能会错过超时:

               should timeout here
                         v
0s         5s         10s        15s
|x - x - x | x - - - - | - - - x -| ...
          true        true       true
但是,IObservable.Throttle每次进入新值时都会自行重置,并且只在经过了一段时间后产生一个值(最后一个传入值)。这可以用作超时并与IObservable合并,以将“超时”值插入流中:

var obs = BaseComms.UDPBaseStringListener(localEP)
            .Where(msg => msg.Data.Contains("running"));

return obs.Merge( obs.Throttle( TimeSpan.FromSeconds(5) )
                        .Select( x => false ) )
            .DistinctUntilChanged();

一个有效的LINQPad示例:

var sub = new Subject<int>();

var script = sub.Timestamp()
    .Merge( sub.Throttle(TimeSpan.FromSeconds(2)).Select( i => -1).Timestamp())
    .Subscribe( x => 
{
    x.Dump("val");
});


Thread.Sleep(1000);

sub.OnNext(1);
sub.OnNext(2);

Thread.Sleep(10000);

sub.OnNext(5);

在2秒超时后,将-1插入到流中。

答案 2 :(得分:1)

我建议避免使用Timeout - 它会导致异常,而异常编码则很糟糕。

此外,似乎只有你的观察值在一个值之后停止才有意义。您可能需要更多地解释您希望行为的内容。

我目前解决您的问题的方法是:

public IObservable<Boolean> GettingUDPMessages(IPEndPoint localEP)
{
    return Observable.Create<bool>(o =>
    {
        var subject = new AsyncSubject<bool>();
        return new CompositeDisposable(
            Observable.Amb(
                BaseComms
                    .UDPBaseStringListener(localEP)
                    .Where(msg => msg.Data.Contains("running"))
                    .Select(s => true),
                Observable
                    .Timer(TimeSpan.FromMilliseconds(10.0))
                    .Select(_ => false)
            ).Take(1).Subscribe(subject), subject.Subscribe(o));
    });
}

这有帮助吗?

答案 3 :(得分:1)

如果您不希望序列停止,只需将其包裹在延迟+重复:

Observable.Defer(() => GettingUDPMessages(endpoint)
    .Repeat();

答案 4 :(得分:0)

根据答案 here,我创建了以下扩展方法:

/// <summary>
/// Emits a value every interval until a value is emitted on the TakeUntil observable. The TakeUntil and Repeat
/// operators work together to create a resetting timer that is reset every time a value is emitted on the
/// TakeUntil observable.
/// </summary>
/// <param name="observable">The observable to observe for timeouts</param>
/// <param name="timeout">The value to use to emit a timeout value</param>
/// <param name="timeoutValue">The value to emit when the observable times out</param>
public static IObservable<T> SelectTimeout<T>(this IObservable<T> observable, TimeSpan timeout, T timeoutValue)
{
    IObservable<T> timeoutObservable = Observable
        .Interval(timeout)
        .Select(_ => timeoutValue)
        .TakeUntil(observable)
        .Repeat();

    return observable.Merge(timeoutObservable);
}

您将需要以下 using 指令:

using System;
using System.Reactive.Linq;

请注意,每次超时后都会发出超时值。如果您只想发出一个超时值,您可以使用 DistinctUntilChanged 方法或查看链接的答案。我更喜欢不断发出超时,因为它更简单、更容易理解。