如何使用Rx(Reactive Extensions)粘贴Observable序列中的相对延迟

时间:2013-10-05 07:26:05

标签: .net delay system.reactive reactive-programming

我正在使用Reactive扩展(版本2.1,以防万一)开始开发,对于我的示例应用程序,我需要按一定间隔推送一系列int值,即每1秒。

我知道,我可以使用Observable.Range<int>(0,10)创建一个序列,但我无法弄清楚如何设置推送之间的相对时间。我已经尝试了Delay()但是在开始时仅将序列移动了一次。

然后我找到了Observable.Generate()方法,可以通过下一步方式调整到此任务:

var delayed = Observable.
              Generate(0, i => i <= 10, i => i + 1, i => i,
                          i => TimeSpan.FromSeconds(1));

但这似乎只适用于简单的'for-each-like'定义序列。 所以,一般来说,我的问题是,我们是否可以获取任何源序列并用一些代理来包装它,这些代理将从源中提取消息并将其推迟进一步推迟?

S--d1--d2--d3--d4--d5-|
D--d1-delay-d2-delay-d3-delay-d4-delay-d5-|

P.S。如果此方法与ReactiveExtensions的概念相矛盾,请另请注意。我不想“无论如何”这样做,并且他们将来会遇到其他一些设计问题。

PPS 一般的想法是确保 输出序列在事件之间具有指定的间隔 ,尽管如果输入序列是有限的或无限的,以及它推动事件的频率。

5 个答案:

答案 0 :(得分:8)

Observable.Interval是您想要查看的内容。它将生成一个基于0的长值,在您指定的每个时间间隔内递增1,例如:

Observable.Interval(TimeSpan.FromSeconds(1)).Subscribe(x => Console.WriteLine(x));

然后,您可以根据需要使用投影(Select)来偏移/更改此值。

您还可以使用Zip运算符将一个流“调整”到另一个流 - 您可能也想查看它。 Zip将来自两个流的事件组合在一起,因此它以当前最慢的流的速度发出。 Zip也非常灵活,它可以压缩任意数量的流,甚至可以将IObservable压缩到IEnumerable。这是一个例子:

var pets = new List<string> { "Dog", "Cat", "Elephant" };
var pace = Observable.Interval(TimeSpan.FromSeconds(1))
    .Zip(pets, (n, p) => p)     
    .Subscribe(x => Console.WriteLine(x), () => Console.WriteLine("Done"));

这会以1秒的间隔写出宠物。

根据P.P.S.如上所述,我将给出另一个答案 - 我将留下这个作为参考,因为它无论如何都是一种有用的技术。

答案 1 :(得分:2)

因此,为了澄清,您希望输出以不超过间隔的速率推送输入,但是否则尽可能快。

在这种情况下试试这个。 input变量构造是一种创建短暂的偶发序列的一种愚蠢的方式,有时更快,有时比2秒的速度慢。请注意,秒表的输出将显示Rx使用的计时器机制中的小不准确性。

var input = Observable.Interval(TimeSpan.FromSeconds(1)).Take(4);
input = input.Concat(Observable.Interval(TimeSpan.FromSeconds(5)).Take(2));

var interval = TimeSpan.FromSeconds(2);

var paced = input.Select(i => Observable.Empty<long>()
                                        .Delay(interval)
                                        .StartWith(i)).Concat();

var stopwatch = new Stopwatch();
stopwatch.Start();
paced.Subscribe(
    x => Console.WriteLine(x + " " + stopwatch.ElapsedMilliseconds),
    () => Console.WriteLine("Done"));

此示例的工作原理是将输入中的每个刻度线投影到一个序列中,该序列在开始时将tick作为单个事件,但在所需的时间间隔内不会OnComplete。然后连接得到的流流。如果输出当前被“刷新”,这种方法可以确保立即发出新的滴答声,但是否则会相互缓冲。

您可以将其包含在扩展方法中,使其成为通用的。

答案 2 :(得分:0)

这是做你想做的最简单的方法:

var delayed =
    source.Do(x => Thread.Sleep(1000));

它会增加第二个延迟,但它会在第一个项目之前执行此操作。你当然可以结束一些逻辑,不要在开始时加减。那不会太难。


这是一种可以预测全新趋势延迟的替代方案。

var delayed =
    Observable.Create<int>(o =>
    {
        var els = new EventLoopScheduler();
        return source
            .ObserveOn(els)
            .Do(x => els.Schedule(() => Thread.Sleep(1000)))
            .Subscribe(o);
    });

答案 3 :(得分:0)

您正在寻找的是Buffer扩展方法。它的签名定义如下:

public static IObservable<IList<TSource>> Buffer<TSource>(
    this IObservable<TSource> source,
    TimeSpan timeSpan)

它将以一种批量生成值的方式转换源序列,频率为timeSpan

答案 4 :(得分:0)

我知道这是一个老问题,但我认为我有正确答案。

压缩一个Observable.Timer生成&#39; ticks&#39;即使源Observable没有产生任何东西。这意味着一旦源生成另一个项目,将使用已生成但尚未消耗的任何刻度。导致生产者以稳定的速度生产物品时会在物品之间增加延迟,但如果生产者有时需要更长的时间来生产物品,这会产生物品爆发。

为了避免这种情况,你需要在你的observable生成的每个项目之间生成一个只生成一个项目的计时器。您可以使用Observable.Switch执行此操作:

var subject = new Subject<Unit>();

        var producer = subject.SelectMany(
                                  _ =>
                                  {
                                      return new[]
                                      {
                                          Observable.Return(true),
                                          Observable.Timer(TimeSpan.FromSeconds(2))
                                                    .Select(q => false)
                                      };
                                  })
                              .Switch();