当我只能生成其他可观察对象时,如何从起始observable生成一个observable?

时间:2011-09-08 14:33:50

标签: c# stack-overflow system.reactive

我想生成一个observable,其中observable的每个值都依赖于它之前的值,从单个值开始。如果我在Func<int, int>之类的值之间进行了简单的转换,则很容易对Observable.Generate这样做:

Func<int, IObservable<int>> mkInts = init =>
    Observable.Generate(
        init,         // start value
        _ => true,    // continue ?
        i => i + 1,   // transformation function
        i => i);      // result selector

using (mkInts(1).Subscribe(Console.WriteLine))
{
    Console.ReadLine();
}

这将很高兴在我的屏幕上写下数字,直到我按下回车键。但是,我的转换函数会执行一些网络IO,因此类型为Func<int, IObservable<int>>,因此我无法使用该方法。相反,我试过这个:

// simulate my transformation function
Func<int, IObservable<int>> mkInt = ts =>
    Observable.Return(ts)
              .Delay(TimeSpan.FromMilliseconds(10));

// pre-assign my generator function, since the function calls itself recursively
Func<int, IObservable<int>> mkInts = null;

// my generator function
mkInts = init =>
{
    var ints = mkInt(init); 

    // here is where I depend on the previous value.
    var nextInts = ints.SelectMany(i => mkInts(i + 1)); 
    return ints.Concat(nextInts);
};

using (mkInts(1).Subscribe(Console.WriteLine))
{
    Console.ReadLine();
}

但是这会在打印大约5000个数字后出现堆栈溢出。我该如何解决这个问题?

5 个答案:

答案 0 :(得分:3)

我想我有一个很好的清洁解决方案。

首先,请回到使用Func<int, int> - 使用Func<int, IObservable<int>>可以轻松将其转换为Observable.FromAsyncPattern

我用它进行测试:

Func<int, int> mkInt = ts =>
{
    Thread.Sleep(100);
    return ts + 1;
};

现在这里是赚钱的人:

Func<int, Func<int, int>, IObservable<int>> mkInts = (i0, fn) =>
    Observable.Create<int>(o =>
    {
        var ofn = Observable
            .FromAsyncPattern<int, int>(
                fn.BeginInvoke,
                fn.EndInvoke);

        var s = new Subject<int>();

        var q = s.Select(x => ofn(x)).Switch();

        var r = new CompositeDisposable(new IDisposable[]
        {
            q.Subscribe(s),
            s.Subscribe(o),
        });

        s.OnNext(i0);

        return r;
    });

迭代函数变为异步可观察。

q变量将主题中的值提供给可观察的迭代函数,并选择计算出的observable。 Switch方法使结果变平,并确保正确清理对可观察迭代函数的每次调用。

此外,使用CompositeDisposable允许将两个订阅作为一个处理。非常整洁!

这很容易使用:

using (mkInts(7, mkInt).Subscribe(Console.WriteLine))
{
    Console.ReadLine();
}

现在您拥有生成器函数的完全参数化版本。很好,是吗?

答案 1 :(得分:3)

我发现following答案是正确的,但有点太复杂了。 我建议的唯一变化是mkInts方法:

Func<int, Func<int, int>, IObservable<int>> mkInts = (i0, fn) =>
   {
      var s = new Subject<int>();
      s.ObserveOn(Scheduler.NewThread).Select(fn).Subscribe(s);
      s.OnNext(i0);
      return s;
   };

答案 2 :(得分:1)

我不完全确定你是否打算再将函数的最终结果反馈到函数中,或者如果你想要一个单独的函数来获取下一个输入,那么我做了两个。这里的诀窍是让IScheduler重复召唤重复的电话。

public Func<T, IObservable<T>> Feedback<T>(Func<T, IObservable<T>> generator, 
                                           IScheduler scheduler)
{
    return seed =>
             Observable.Create((IObserver<T> observer) =>
                 scheduler.Schedule(seed,
                     (current, self) =>
                         generator(current).Subscribe(value => 
                            {
                                observer.OnNext(value);
                                self(value);
                            })));
}

public Func<T, IObservable<T>> GenerateAsync<T>(Func<T, IObservable<T>> generator,
                                                Func<T, T> seedTransform,
                                                IScheduler scheduler)
{
    return seed =>
             Observable.Create((IObserver<T> observer) =>
                 scheduler.Schedule(seed,
                     (current, self) =>
                         generator(current).Subscribe(value =>
                         {
                             observer.OnNext(value);
                             self(seedTransform(current));
                         })));
}

答案 3 :(得分:1)

我认为代码不是尾递归,因此导致SO异常。下面是没有任何此类例外的正常工作的代码。

public static IObservable<int> GetObs(int i)
{
   return Observable.Return(i).Delay(TimeSpan.FromMilliseconds(10));
}
public static IObservable<int> MakeInts(int start)
{
   return Observable.Generate(start, _ => true, i => i + 1, i => GetObs(i))
                .SelectMany(obs => obs);
}


using (MakeInts(1).Subscribe(Console.WriteLine))
{
    Console.ReadLine();
}

或者修改您的代码,如:

Action<int, IObserver<int>> mkInt = (i,obs) =>
               Observable.Return(i)
              .Delay(TimeSpan.FromMilliseconds(10)).Subscribe<int>(ii => obs.OnNext(ii));

            // pre-assign my generator function, since the function calls itself recursively
            Func<int, IObservable<int>> mkInts = null;
            // my generator function
            mkInts = init =>
            {
                var s = new Subject<int>();
                var ret = s.Do(i => {
                    mkInt(i + 1, s);
                });
                mkInt(init, s);
                return ret;
            };

            using (mkInts(1).Subscribe(Console.WriteLine))
            {
                Console.ReadLine();
            }

答案 4 :(得分:0)

我找到了一个解决方案,虽然它可能不是最漂亮的,但却能达到我想要的效果。如果有人有更好的解决方案,我会将其标记为答案。

Func<int, IObservable<int>> mkInt = ts =>
    Observable.Return(ts)
              .Delay(TimeSpan.FromMilliseconds(10));

Func<int, IObservable<int>> mkInts = init =>
{
    Subject<int> subject = new Subject<int>();
    IDisposable sub = null;
    Action<int> onNext = null;
    onNext = i =>
    {
        subject.OnNext(i);
        sub.Dispose();
        sub = mkInt(i + 1).Subscribe(onNext);
    };
    sub = mkInt(init).Subscribe(onNext);
    return subject;
};

using (mkInts(1).Subscribe(Console.WriteLine))
{
    Console.ReadLine();
}