【问题标题】:Throttle Rx.Observable without skipping values节流 Rx.Observable 不跳过值
【发布时间】:2017-10-08 01:53:54
【问题描述】:

Throttle 方法会在其他人跟随得太快时跳过可观察序列中的值。但我需要一种方法来延迟它们。也就是说,我需要设置项目之间的最小延迟,而不跳过任何项目

实际示例:有一个 Web 服务接受请求的速度不超过每秒一次;有一个用户可以添加请求,单个或批量。如果没有 Rx,我将创建一个列表和一个计时器。当用户添加请求时,我会将它们添加到列表中。在计时器事件中,我会检查列表是否为空。如果不是,我将发送请求并删除相应的项目。有锁和所有的东西。现在,使用 Rx,我可以创建 Subject,在用户添加请求时添加项目。但我需要一种方法来确保 Web 服务不会因应用延迟而泛滥。

我是 Rx 的新手,所以我可能遗漏了一些明显的东西。

【问题讨论】:

    标签: c# .net system.reactive


    【解决方案1】:

    使用EventLoopScheduler 有一个相当简单的方法来做你想做的事。

    我从一个 observable 开始,它将每 0 到 3 秒随机产生一次值。

    var rnd = new Random();
    
    var xs =
        Observable
            .Generate(
                0,
                x => x < 20,
                x => x + 1,
                x => x,
                x => TimeSpan.FromSeconds(rnd.NextDouble() * 3.0));
    

    现在,为了立即生成这个输出值,除非最后一个值在一秒钟前我这样做了:

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

    这有效地观察了EventLoopScheduler 上的源,然后在每个OnNext 之后将其休眠1 秒钟,这样它就只能在唤醒后开始下一个OnNext

    我测试过它可以与这段代码一起使用:

    ys
        .Timestamp()
        .Select(x => x.Timestamp.Second + (double)x.Timestamp.Millisecond/1000.0)
        .Subscribe(x => Console.WriteLine(x));
    

    我希望这会有所帮助。

    【讨论】:

    • Thread.Sleep 不好吗?我一直认为为本质上是“计时器”的东西暂停线程是浪费资源。
    • @VarvaraKalinina - 它发生在它自己的线程上,并且没有使其他任何事情陷入僵局。在这种情况下很好。
    • 我没有说这是关于死锁或阻塞的东西。这是关于资源浪费。你让一个线程几乎一直强制地什么都不做。如果不被阻止,它可以做其他事情
    【解决方案2】:

    一个简单的扩展方法怎么样:

    public static IObservable<T> StepInterval<T>(this IObservable<T> source, TimeSpan minDelay)
    {
        return source.Select(x => 
            Observable.Empty<T>()
                .Delay(minDelay)
                .StartWith(x)
        ).Concat();
    }
    

    用法:

    var bufferedSource = source.StepInterval(TimeSpan.FromSeconds(1));
    

    【讨论】:

      【解决方案3】:

      我想建议一种使用Observable.Zip的方法:

      // Incoming requests
      var requests = new[] {1, 2, 3, 4, 5}.ToObservable();
      
      // defines the frequency of the incoming requests
      // This is the way to emulate flood of incoming requests.
      // Which, by the way, uses the same approach that will be used in the solution
      var requestsTimer = Observable.Interval(TimeSpan.FromSeconds(0.1)); 
      var incomingRequests = Observable.Zip(requests, requestsTimer, (number, time) => {return number;});
      incomingRequests.Subscribe((number) =>
      {
          Console.WriteLine($"Request received: {number}");
      });
      
      // This the minimum interval at which we want to process the incoming requests
      var processingTimeInterval = Observable.Interval(TimeSpan.FromSeconds(1));
      
      // Zipping incoming requests with the interval
      var requestsToProcess = Observable.Zip(incomingRequests, processingTimeInterval, (data, time) => {return data;});
      
      requestsToProcess.Subscribe((number) =>
      {
          Console.WriteLine($"Request processed: {number}");
      });
      

      【讨论】:

      • 我正在寻找 rxjs 的解决方案,这也适用于那里。非常感谢! :)
      • 但是,在初始流中可能 100 秒内没有任何记录的情况下,这项工作是否可行,然后您会在一秒钟内获得一千条记录。这些仍然会受到限制,还是会立即全部处理,因为 Interval Observable 已经有一个可以立即压缩的积压工作?
      【解决方案4】:

      我在玩这个,发现 .Zip(如前所述)是最简单的方法:

      var stream = "ThisFastObservable".ToObservable();
      var slowStream = 
          stream.Zip(
              Observable.Interval(TimeSpan.FromSeconds(1)), //Time delay 
              (x, y) => x); // We just care about the original stream value (x), not the interval ticks (y)
      
      slowStream.TimeInterval().Subscribe(x => Console.WriteLine($"{x.Value} arrived after {x.Interval}"));
      

      输出:

      T arrived after 00:00:01.0393840
      h arrived after 00:00:00.9787150
      i arrived after 00:00:01.0080400
      s arrived after 00:00:00.9963000
      F arrived after 00:00:01.0002530
      a arrived after 00:00:01.0003770
      s arrived after 00:00:00.9963710
      t arrived after 00:00:01.0026450
      O arrived after 00:00:00.9995360
      b arrived after 00:00:01.0014620
      s arrived after 00:00:00.9993100
      e arrived after 00:00:00.9972710
      r arrived after 00:00:01.0001240
      v arrived after 00:00:01.0016600
      a arrived after 00:00:00.9981140
      b arrived after 00:00:01.0033980
      l arrived after 00:00:00.9992570
      e arrived after 00:00:01.0003520
      

      【讨论】:

        【解决方案5】:

        使用可观察计时器从阻塞队列中获取数据怎么样?下面的代码未经测试,但应该让您了解我的意思...

        //assuming somewhere there is 
        BlockingCollection<MyWebServiceRequestData> workQueue = ...
        
        Observable
          .Timer(new TimeSpan(0,0,1), new EventLoopScheduler())
          .Do(i => myWebService.Send(workQueue.Take()));
        
        // Then just add items to the queue using workQueue.Add(...)
        

        【讨论】:

        • 此解决方案可能会起作用,并且类似于问题中的“before rx”解决方案,但有一个缺点:即使有可能,也不会立即发送请求;只有当计时器滴答作响时。 1 秒的延迟不是很重要,但如果请求之间需要延迟,例如 1 分钟,那么这是一个问题。
        • @Athari 不正确。第一个计时器滴答将在 Take() 上阻塞,并将在项目入队时执行。您可以尝试将超时设置为 1 分钟以了解我的意思。
        • 我试过了,实际上还有一个问题:有时两个项目一个接一个地处理,没有延迟。猜测是因为在等待 Take 时,另一个计时器事件排队等待前一个事件完成处理。我的代码:pastebin.com/RRZ0ffBB
        【解决方案6】:
        .Buffer(TimeSpan.FromSeconds(0.2)).Where(i => i.Any())
        .Subscribe(buffer => 
        {
             foreach(var item in buffer) Console.WriteLine(item)
        });
        

        【讨论】:

        • 这里也有同样的问题:当你只需要 1 项或什么都不需要时返回序列的开销(等待然后返回下一项,依此类推)
        猜你喜欢
        • 2012-08-03
        • 2022-07-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-11-10
        • 2011-12-13
        • 1970-01-01
        相关资源
        最近更新 更多