C# 可观测数据的缓冲以稳定较慢观测器的可变延迟

C# 可观测数据的缓冲以稳定较慢观测器的可变延迟,c#,.net,observable,system.reactive,producer-consumer,C#,.net,Observable,System.reactive,Producer Consumer,我有一个可观察到的,它产生一系列数字,数字之间的延迟范围为0到1秒(随机): 如果生产者每0到1秒生成一个数字,消费者每2秒只能消费一个数字,那么很明显生产者生成数字的速度比消费者消费数字的速度快(背压)。我想做的是能够提前“预加载”例如10或20个来自生产者的数字(如果消费者不能足够快地处理它们),以便消费者能够在没有随机延迟的情况下使用它们(但不是全部,因为可观察序列是无限的,如果它运行一段时间,我们的内存就会耗尽) 如果我有一个较慢的消费者,这将有点稳定来自生产者的可变延迟。然而,我想不

我有一个可观察到的,它产生一系列数字,数字之间的延迟范围为0到1秒(随机):


如果生产者每0到1秒生成一个数字,消费者每2秒只能消费一个数字,那么很明显生产者生成数字的速度比消费者消费数字的速度快(背压)。我想做的是能够提前“预加载”例如10或20个来自生产者的数字(如果消费者不能足够快地处理它们),以便消费者能够在没有随机延迟的情况下使用它们(但不是全部,因为可观察序列是无限的,如果它运行一段时间,我们的内存就会耗尽)

如果我有一个较慢的消费者,这将有点稳定来自生产者的可变延迟。然而,我想不出一个可能的解决方案,如何使用ReactiveX中的运算符来实现这一点,我已经查看了
缓冲区
示例
去盎司
窗口
的文档,它们都不是我要找的东西

有没有关于如何做到这一点的想法?请注意,即使我的observer代码对于
async
/
await
,也不是很正确,但我想不出更好的方法来说明我要实现的目标


编辑:

,我可能没有很好地阐述这个问题,因为它看起来更像一个XY问题。对此我很抱歉。我将尝试用另一个例子来说明我试图建模的过程。我真的不太关心生产者或消费者方面的确切时间延迟。时间延迟实际上只是一些需要一些时间的异步工作的占位符

我更多考虑的是一种一般模式,生产者以某种可变速率生产商品,我想处理所有商品,消费者也只能以某种可变速率消费商品。我正试图更有效地做到这一点


我将尝试用一个更真实的比萨饼店示例来说明这一点以下是使用纯Rx时观察到的情况:

var producer = Observable.Generate(
    (r: new Random(), i: 0),                    // initial state
    _ => true,                                  // condition
    t => (t.r, t.i + 1),                        // iterator
    t => t.i,                                   // result selector
    t => TimeSpan.FromSeconds(t.r.NextDouble()) // timespan generator
);

var consumer = producer.Zip(
    Observable.Interval(TimeSpan.FromSeconds(2)),
    (_, i) => i
);
然而,要“毫不迟疑地抓住第一个n”并非易事。因此,我们可以创建一个无时间间隔的生产者:

var rawProducer = Observable.Range(0, int.MaxValue);
然后分别创建时间间隔:

var timeGaps = Observable.Repeat(TimeSpan.Zero).Take(10) //or 20
    .Concat(Observable.Generate(new Random(), r => true, r => r, r => TimeSpan.FromSeconds(r.NextDouble())));
然后将这两者结合起来:

var timeGappedProducer = rawProducer.Zip(timeGaps, (i, ts) => Observable.Return(i).Delay(ts))
    .Concat();
消费者看起来基本相同:

var lessPressureConsumer = timeGappedProducer .Zip(
    Observable.Interval(TimeSpan.FromSeconds(2)),
    (_, i) => i
);


考虑到这些,我真的不明白你为什么要这么做。这不是一个处理背压的好方法,这个问题听起来有点奇怪。您提到的操作员(
Sample
Throttle
等)是处理背压的更好方法。

您所描述的问题非常适合于生产者和消费者之间共享的简单有界缓冲区。生产者必须有一个与写入缓冲区相关的条件,说明缓冲区不能满。使用者必须具有声明缓冲区不能为空的条件。 请参见下面使用Ada语言的示例

with Ada.Text_IO; use Ada.Text_IO;

procedure Main is
   type Order_Nums is range 1..10_000;
   Type Index is mod 10;
   type Buf_T is array(Index) of Order_Nums;

   protected Orders is
      entry Prepare(Order : in Order_Nums);
      entry Sell(Order : out Order_Nums);
   private
      Buffer  : Buf_T;
      P_Index : Index := Index'First;
      S_Index : Index := Index'First;
      Count   : Natural := 0;
   end Orders;

   protected body Orders is
      entry Prepare(Order : in Order_Nums) when Count < Index'Modulus is
      begin
         Buffer(P_Index) := Order;
         P_Index := P_Index + 1;
         Count := Count + 1;
      end Prepare;

      entry Sell(Order : out Order_Nums) when Count > 0 is
      begin
         Order := Buffer(S_Index);
         S_Index := S_Index + 1;
         Count := Count - 1;
      end Sell;
   end Orders;

   task Chef is
      Entry Stop;
   end Chef;

   task Seller is
      Entry Stop;
   end Seller;

   task body Chef is
      The_Order : Order_Nums := Order_Nums'First;
   begin
      loop
         select
            accept Stop;
            exit;
         else
            delay 1.0; -- one second
            Orders.Prepare(The_Order);
            Put_Line("Chef made order number " & The_Order'Image);
            The_Order := The_Order + 1;
            exit when The_Order = Order_Nums'Last;
         end select;
      end loop;
   end Chef;

   task body Seller is
      The_Order : Order_Nums;
   begin
      loop
         select
            accept Stop;
            exit;
         else
            delay 2.0; -- two seconds
            Orders.Sell(The_Order);
            Put_Line("Sold order number " & The_Order'Image);
         end select;
      end loop;
   end Seller;

begin
   delay 60.0; -- 60 seconds
   Chef.Stop;
   Seller.Stop;
end Main;

你能展示输入流是什么样子,输出流应该是什么样子吗?生产者产生的数字之间有0到1秒的延迟,消费者应该消费这些数字,但这样做需要2秒,然后写出来。我不想让它每2秒等待随机的0到1秒延迟消费者准备好了另一个数字…为什么缓冲区或窗口对你不起作用?@PauloMorgado我不确定,当我查看它们时,似乎缓冲区返回
IObservable
,而窗口返回
IObservable
,但我的观察者所使用的只是
IObservable
。另外,我有点想象缓冲将以一种更“滑动”的方式工作,例如,不只是将其分成20个项目的块,而是在有时间的情况下预加载多达20个项目,当我从缓冲区中取出一个时,只需加载另一个,更像是一个大小受限的队列。使用缓冲区/窗口是否确实可以实现?你能给我指一下正确的方向吗?
SelectMany
可以解决你大部分的问题。看看这个()并尝试在你的问题中添加一个你想要的图表。这正是我要给出的答案。我想补充一点,使用
producer.Window(TimeSpan.FromSeconds(2.0))
可能是调节值生成的好方法。像
producer.Window(TimeSpan.FromSeconds(2.0)).Select(x=>x.ToArray()).SelectMany(x=>x)
这样的查询将每隔2秒从
producer
生成数字数组。感谢您的回答,非常感谢。你说得对,我可能没有很好地阐述这个问题。我编辑了我的问题,添加了一个更真实的过程示例,我正在尝试建模,以更好地说明我的想法。是的,谢谢。我想你明白我想要的模式。我只是想知道如何使用Rx.NET和Observable实现这个模式。
var lessPressureConsumer = timeGappedProducer .Zip(
    Observable.Interval(TimeSpan.FromSeconds(2)),
    (_, i) => i
);
with Ada.Text_IO; use Ada.Text_IO;

procedure Main is
   type Order_Nums is range 1..10_000;
   Type Index is mod 10;
   type Buf_T is array(Index) of Order_Nums;

   protected Orders is
      entry Prepare(Order : in Order_Nums);
      entry Sell(Order : out Order_Nums);
   private
      Buffer  : Buf_T;
      P_Index : Index := Index'First;
      S_Index : Index := Index'First;
      Count   : Natural := 0;
   end Orders;

   protected body Orders is
      entry Prepare(Order : in Order_Nums) when Count < Index'Modulus is
      begin
         Buffer(P_Index) := Order;
         P_Index := P_Index + 1;
         Count := Count + 1;
      end Prepare;

      entry Sell(Order : out Order_Nums) when Count > 0 is
      begin
         Order := Buffer(S_Index);
         S_Index := S_Index + 1;
         Count := Count - 1;
      end Sell;
   end Orders;

   task Chef is
      Entry Stop;
   end Chef;

   task Seller is
      Entry Stop;
   end Seller;

   task body Chef is
      The_Order : Order_Nums := Order_Nums'First;
   begin
      loop
         select
            accept Stop;
            exit;
         else
            delay 1.0; -- one second
            Orders.Prepare(The_Order);
            Put_Line("Chef made order number " & The_Order'Image);
            The_Order := The_Order + 1;
            exit when The_Order = Order_Nums'Last;
         end select;
      end loop;
   end Chef;

   task body Seller is
      The_Order : Order_Nums;
   begin
      loop
         select
            accept Stop;
            exit;
         else
            delay 2.0; -- two seconds
            Orders.Sell(The_Order);
            Put_Line("Sold order number " & The_Order'Image);
         end select;
      end loop;
   end Seller;

begin
   delay 60.0; -- 60 seconds
   Chef.Stop;
   Seller.Stop;
end Main;
Chef made order number  1
Sold order number  1
Chef made order number  2
Chef made order number  3
Sold order number  2
Chef made order number  4
Chef made order number  5
Sold order number  3
Chef made order number  6
Chef made order number  7
Sold order number  4
Chef made order number  8
Chef made order number  9
Sold order number  5
Chef made order number  10
Chef made order number  11
Sold order number  6
Chef made order number  12
Chef made order number  13
Sold order number  7
Chef made order number  14
Chef made order number  15
Sold order number  8
Chef made order number  16
Chef made order number  17
Sold order number  9
Chef made order number  18
Chef made order number  19
Sold order number  10
Chef made order number  20
Sold order number  11
Chef made order number  21
Sold order number  12
Chef made order number  22
Chef made order number  23
Sold order number  13
Sold order number  14
Chef made order number  24
Sold order number  15
Chef made order number  25
Sold order number  16
Chef made order number  26
Chef made order number  27
Sold order number  17
Chef made order number  28
Sold order number  18
Chef made order number  29
Sold order number  19
Sold order number  20
Chef made order number  30
Sold order number  21
Chef made order number  31
Chef made order number  32
Sold order number  22
Sold order number  23
Chef made order number  33
Sold order number  24
Chef made order number  34
Sold order number  25
Chef made order number  35
Sold order number  26
Chef made order number  36
Chef made order number  37
Sold order number  27
Sold order number  28
Chef made order number  38
Sold order number  29
Chef made order number  39
Sold order number  30
Chef made order number  40
Sold order number  31