“长”间隔后完整的可观察序列

时间:2019-02-01 20:10:23

标签: c# asynchronous system.reactive

以下可观察的序列将每个元素添加到ReplaySubject中,以便我以后可以访问任何元素,甚至可以等待ReplaySubject完成。到达时间跨度后,它将完成ReaplySubject。

ReplaySubject<string> rfidPlayer = new ReplaySubject<string>();
characteristic.WhenNotificationReceived()
    .TakeUntil(Observable.Timer(TimeSpan.FromSeconds(1)))
    .Subscribe(
        onNext: result =>
        {
            string nextTag = BitConverter.ToString(result.Data);
            nextTag = nextTag.Replace("-", "");

            rfidPlayer.OnNext(nextTag);
        },
        onCompleted: () =>
        {
            rfidPlayer.OnCompleted();
        });

我希望序列运行到上一次“ OnNext”调用以来的给定时间,然后完成。这在各种蓝牙通信场景中非常有用,其中蓝牙设备会给我一系列数据,然后停下来而没有任何类型的完成消息或事件。在这些情况下,我需要试探性地确定何时完成序列,然后自己完成。因此,如果自上次蓝牙通​​知以来“太长”,我想完成ReplaySubject。

我可以这样创建一个计时器,在收到每个元素时将其重置,然后在计时器到达“太长”时完成ReplaySubject,但是我听说创建一个对象并在可观察范围内对其进行操作订阅不是线程安全的。

关于“间隔”过长后如何完成序列的任何建议?

这里是一个与我所听到的内容相比不是线程安全的版本,但应能按预期工作:

bool reading = true;
System.Timers.Timer timer = new System.Timers.Timer(1000);
timer.Elapsed += (sender, e) =>
{
    reading = false;
};

ReplaySubject<string> rfidPlayer = new ReplaySubject<string>();
characteristic.WhenNotificationReceived()
    .TakeWhile(x => reading)
    .Subscribe(
        onNext: result =>
        {
            string nextTag = BitConverter.ToString(result.Data);
            nextTag = nextTag.Replace("-", "");

            timer.Stop();
            timer.Start();

            rfidPlayer.OnNext(nextTag);
        },
        onCompleted: () =>
        {
            rfidPlayer.OnCompleted();
        });

基于Simonare的第一个答案,这似乎令人满意:

                characteristic.WhenNotificationReceived()
                .Timeout(TimeSpan.FromSeconds(1))
                .Subscribe(
                    onNext: result =>
                    {
                        string nextTag = BitConverter.ToString(result.Data);
                        nextTag = nextTag.Replace("-", "");

                        rfidPlayer.OnNext(nextTag);
                    },
                    onError: error =>
                    {
                        rfidPlayer.OnCompleted();
                    });

3 个答案:

答案 0 :(得分:0)

您可以考虑使用Timeout Operator 。唯一的缺点是它会以错误信号终止。您可能需要处理错误

  

如果该Observable在指定的时间段内未发出任何项目,则Timeout运算符允许您终止具有onError终止的Observable。

如果使用下面的方法,则可以克服错误

 .Timeout(200, Promise.resolve(42));
  

另一种变体使您可以指示超时切换到您指定的备用Observable,而不是在触发超时条件时以错误终止。


characteristic.WhenNotificationReceived()
    .Timeout(TimeSpan.FromSeconds(1))
    .Subscribe(
        onNext: result =>
        {
            ....
            rfidPlayer.OnNext(....);
        },
        onError: error =>
        {
            rfidPlayer.OnCompleted();
        });

答案 1 :(得分:0)

由于异常,我觉得使用Timeout很讨厌。

我更喜欢在序列中插入一个值,以终止序列。例如,如果我的序列产生非负数,那么如果我插入-1,我知道结束该序列。

这是一个例子:

从此可观察对象开始,它以1开头的2的幂,并且还将每个值的产生延迟了待定值的毫秒数。

Observable
    .Generate(1, x => true, x => 2 * x, x => x, x => TimeSpan.FromMilliseconds(x))

所以1、2、4、8等越来越慢。

现在,如果3.0秒内没有值,我想停止此序列,那么我可以这样做:

    .Select(x => Observable.Timer(TimeSpan.FromSeconds(3.0)).Select(y => -1).StartWith(x))
    .Switch()
    .TakeWhile(x => x >= 0)

如果运行此序列,我将得到以下输出:

1 
2 
4 
8 
16 
32 
64 
128 
256 
512 
1024 
2048 

该序列即将产生4096,但它首先要等待4096毫秒才能产生该值-同时Observable.Timer(TimeSpan.FromSeconds(3.0))会触发并输出-1,从而停止顺序。

此查询的关键部分是使用Switch。它仅通过订阅最新的外部可观察项并从先前的订阅中取消订阅,来获取IObservable<IObservable<T>>并产生IObservable<T>

因此,在我的查询中,序列产生的每个新值都会停止并重新启动Timer

在您的情况下,您的可观察性如下所示:

characteristic
    .WhenNotificationReceived()
    .Select(result => BitConverter.ToString(result.Data).Replace("-", ""))
    .Select(x => Observable.Timer(TimeSpan.FromSeconds(1.0)).Select(y => (string)null).StartWith(x))
    .Switch()
    .TakeWhile(x => x != null)
    .Subscribe(rfidPlayer);

答案 2 :(得分:0)

这里是您可以使用的自定义运算符TakeUntilTimeout,它是内置Timeout运算符之上的一薄层。

/// <summary>
/// Applies a timeout policy for each element in the observable sequence.
/// If the next element isn't received within the specified timeout duration
/// starting from its predecessor, the sequence terminates.
/// </summary>
public static IObservable<T> TakeUntilTimeout<T>(
    this IObservable<T> source,
    TimeSpan timeout)
{
    return source.Timeout(timeout, Observable.Empty<T>());
}