我想生成一个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个数字后出现堆栈溢出。我该如何解决这个问题?
答案 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();
}