如何从IObservable请求项目?

时间:2016-01-21 11:20:18

标签: c# functional-programming system.reactive reactive-programming

原帖包含一个问题,我设法解决了,引入了很多共享可变状态的问题。现在,我想知道,是否可以以纯粹的功能方式完成。

请求可以按特定顺序处理。

对于每个订单i,都有效E(i)

处理请求应遵循三个条件

  1. 获取第一个请求和处理第一个请求之间应该没有延迟

  2. 处理某个请求和处理下一个请求之间应该没有延迟

  3. 当有多个处理请求订单时,应选择效率最高的订单

  4. 具体例子:

    对于无限的整数列表,打印它们,因此,素数通常早于素数

    排序的有效性与我们在队列中有素数的次数相反,但是打印非素数

    我在C#中的第一个解决方案(显然不是针对素数)使用了一些具有由并发优先级队列表示的共享可变状态的类。这很丑陋,因为我不得不手动订阅类到事件并取消订阅它们,检查一个中间消费者在其他消费者处理它之前没有用尽队列等等。

    为了重构它,我选择了Reactive Extensions库,它似乎解决了state的问题。我明白在以下情况下我无法使用它:

    source函数不接受任何内容并返回IObservable<Request>

    process函数接受IObservable<Request>并且不返回任何内容

    我必须编写一个reorder函数,该函数会在从sourceprocess的路上重新排序请求。

    内部reorderConcurrentPriorityQueue个订单。它应该处理两种情况:

    1. process忙于处理reorder时,会发现更好的排序并更新队列

    2. process请求新订单时reorder返回队列中的第一个元素

    3. 问题在于,如果reorder返回IObservable<Request>,它就会意识不到,无论是从中请求了项目,还是没有。

      如果reorder在收到后立即致电OnNext,则不会对任何内容重新排序并违反条件3.

      如果确定,它已找到最佳排序,则违反了条件1和2,因为process可能会闲置。

      如果reorder返回ISubject<Request>,则会向用户提示选择OnErrorOnCompleted

      如果reorder已经返回队列,我会回到我开始的地方

      问题是冷IObservable.Create不够懒惰。当订阅它时,它开始耗尽所有请求的队列,但只使用了第一个请求的结果。

      我提出的解决方案是返回可观察的请求,即IObservable<Func<Task<int>>>而不是IObservable<int>

      当只有一个订阅者时,它可以正常工作,但是如果使用的请求数量多于源数据生成的数量,那么它们将永远等待。

      这个问题可以通过引入缓存来解决,但是快速消耗队列的消费者会对所有其他消费者产生副作用,因为他会在一些等待之后以较低的有效顺序冻结队列。 / p>

      所以,我会在最初的问题上发布解决方案,但它并不是一个非常有价值的答案,因为它会引入很多的问题。

      这说明了为什么功能性反应式编程和副作用不能很好地混合。另一方面,我现在似乎有一个无法用纯粹的功能方式解决的实际问题的例子。或者不是吗?如果Order函数接受optimizationLevel作为参数,则它将是纯粹的。我们能否以某种方式隐含地将时间转换为optimizationLevel以使其变得纯净?

      我非常希望看到这样的解决方案。使用C#或任何其他语言。

      有问题的解决方案。使用来自this repo。

      的ConcurrentPriorityQueue
      using System;
      using System.Collections.Generic;
      using System.Threading.Tasks;
      using System.Reactive.Linq;
      using DataStructures;
      using System.Threading;
      
      namespace LazyObservable
      {
          class Program
          {
              /// <summary>
              /// Compares tuple by second element, then by first in reverse
              /// </summary>
              class PriorityComparer<TElement, TPriority> : IComparer<Tuple<TElement, TPriority>>
                  where TPriority : IComparable<TPriority>
              {
                  Func<TElement, TElement, int> fallbackComparer;
                  public PriorityComparer(IComparer<TElement> comparer=null)
                  {
                      if (comparer != null)
                      {
                          fallbackComparer = comparer.Compare;
                      }
                      else if (typeof(IComparable<TElement>).IsAssignableFrom(typeof(TElement))
                          || typeof(IComparable).IsAssignableFrom(typeof(TElement)))
                      {
                          fallbackComparer = (a,b)=>-Comparer<TElement>.Default.Compare(a,b);
                      }
                      else
                      {
                          fallbackComparer = (_1,_2) => 0;
                      }
                  }
                  public int Compare(Tuple<TElement, TPriority> x, Tuple<TElement, TPriority> y)
                  {
                      if (x == null && y == null)
                      {
                          return 0;
                      }
                      if (x == null || y == null)
                      {
                          return x == null ? -1 : 1;
                      }
                      int res=x.Item2.CompareTo(y.Item2);
                      if (res == 0)
                      {
                          res = fallbackComparer(x.Item1,y.Item1);
                      }
                      return res;
                  }
              };
              const int N = 100;
              static IObservable<int> Source()
              {
                  return Observable.Interval(TimeSpan.FromMilliseconds(1))
                      .Select(x => (int)x)
                      .Where(x => x <= 100);
              }
              static bool IsPrime(int x)
              {
                  if (x <= 1)
                  {
                      return false;
                  }
                  if (x == 2)
                  {
                      return true;
                  }
                  int limit = ((int)Math.Sqrt(x)) + 1;
                  for (int i = 2; i < limit; ++i)
                  {
                      if (x % i == 0)
                      {
                          return false;
                      }
                  }
                  return true;
              }
              static IObservable<Func<Task<int>>> Order(IObservable<int> numbers)
              {
                  ConcurrentPriorityQueue<Tuple<int, int>> queue = new ConcurrentPriorityQueue<Tuple<int, int>>(new PriorityComparer<int, int>());
                  numbers.Subscribe(x =>
                  {
                      queue.Add(new Tuple<int, int>(x, 0));
                  });
                  numbers
                      .ForEachAsync(x=>
                      {
                          Console.WriteLine("Testing {0}", x);
                          if (IsPrime(x))
                          {
                              if (queue.Remove(new Tuple<int, int>(x, 0)))
                              {
                                  Console.WriteLine("Accelerated {0}", x);
                                  queue.Add(new Tuple<int, int>(x, 1));
                              }
                          }
                      });
                  Func<Task<int>> requestElement = async () =>
                    {
                        while (queue.Count == 0)
                        {
                            await Task.Delay(30);
                        }
                        return queue.Take().Item1;
                    };
                  return numbers.Select(_=>requestElement);
              }
              static void Process(IObservable<Func<Task<int>>> numbers)
              {
                  numbers
                      .Subscribe(async x=>
                      {
                          await Task.Delay(1000);
                          Console.WriteLine(await x());
                      });
              }
      
              static void Main(string[] args)
              {
                  Console.WriteLine("init");
                  Process(Order(Source()));
                  //Process(Source());
                  Console.WriteLine("called");
                  Console.ReadLine();
              }
          }
      }
      

1 个答案:

答案 0 :(得分:0)

总结(概念上):

  1. 您有不规则的请求(来自source),以及可以处理它们的单个处理器(函数process)。
  2. 处理器应该没有停机时间。
  3. 你隐含地需要某种队列式的集合来管理请求进入速度比处理器可以处理的速度快的情况。
  4. 如果有多个请求排队,理想情况下,您应该通过一些有效功能对它们进行排序,但重新排序不应该是停机的原因。 (函数reorder)。
  5. 这一切都正确吗?

    假设它是source可以是IObservable<Request>类型,听起来不错。 reorder虽然听起来确实应该返回IEnumerable<Request>process想要以拉取为基础:它想要在释放后拉出最高优先级请求,并等待如果队列为空但立即启动的下一个请求。这听起来像是IEnumerable的任务,而不是IObservable

    public IObservable<Request> requester();
    public IEnumerable<Request> reorder(IObservable<Request> requester);
    public void process(IEnumerable<Request> requestEnumerable);