[英]How to convert IEnumerable to IObservable using HistoricalScheduler
I have an IEnumerable<T>
where T allows derivation of a relevant timestamp. 我有一个IEnumerable<T>
,其中T允许推导相关的时间戳。
I would like to convert that to an IObservable<T>
but I wish to use the HistoricalScheduler
so that notifications occur as per the derived timestamp. 我想将其转换为IObservable<T>
但我希望使用HistoricalScheduler
以便根据派生的时间戳发生通知。 Doing this allows use of built-in RX methods for windowing, sliding windows, etc., which are ultimately what I am trying to utilise. 这样做允许使用内置的RX方法进行窗口化,滑动窗口等,这最终是我想要利用的。
Many of the suggestions of how to go about this suggest using Generate()
. 关于如何进行此操作的许多建议建议使用Generate()
。 However, this method causes StackOverflowExceptions . 但是,此方法会导致StackOverflowExceptions 。 Eg: 例如:
static void Main(string[] args)
{
var enumerable = Enumerable.Range(0, 2000000);
var now = DateTimeOffset.Now;
var scheduler = new HistoricalScheduler(now);
var observable = Observable.Generate(
enumerable.GetEnumerator(),
e => e.MoveNext(),
e => e,
e => Timestamped.Create(e, now.AddTicks(e.Current)),
e => now.AddTicks(e.Current),
scheduler);
var s2 = observable.Count().Subscribe(eventCount => Console.WriteLine("Found {0} events @ {1}", eventCount, scheduler.Now));
scheduler.Start();
s2.Dispose();
Console.ReadLine();
}
This will result in a stack overflow. 这将导致堆栈溢出。
The standard ToObservable()
method cannot be used because, although it allows a custom scheduler to be specified, it does not provide any mechanism to control how the resulting notifications are scheduled on that scheduler. 无法使用标准的ToObservable()
方法,因为尽管它允许指定自定义调度程序,但它不提供任何机制来控制如何在该调度程序上调度生成的通知。
How do I convert an IEnumerable
to an IObservable
with explicity scheduled notification? 如何将IEnumerable
转换为具有明确预定通知的IObservable
?
Attempted to use Asti's code in the following test: 在以下测试中尝试使用Asti的代码:
static void Main(string[] args)
{
var enumerable = Enumerable.Range(0, 2000000);
var now = DateTimeOffset.Now;
var series = enumerable.Select(i => Timestamped.Create(i, now.AddSeconds(i)));
var ticks = Observable.Interval(TimeSpan.FromSeconds(1)).Select(i => now.AddSeconds(i));
var scheduler = new HistoricalScheduler(now);
Playback(series,ticks,scheduler).Subscribe(Console.WriteLine);
scheduler.Start();
}
However it throws an ArgumentOutOfRangeException
: 但是它会抛出ArgumentOutOfRangeException
:
Specified argument was out of the range of valid values.
Parameter name: time
at System.Reactive.Concurrency.VirtualTimeSchedulerBase`2.AdvanceTo(TAbsolute time)
at System.Reactive.AnonymousSafeObserver`1.OnNext(T value)
at System.Reactive.Linq.ObservableImpl.Select`2._.OnNext(TSource value)
at System.Reactive.Linq.ObservableImpl.Timer.TimerImpl.Tick(Int64 count)
at System.Reactive.Concurrency.DefaultScheduler.<>c__DisplayClass7_0`1.<SchedulePeriodic>b__1()
at System.Reactive.Concurrency.AsyncLock.Wait(Action action)
at System.Reactive.Concurrency.DefaultScheduler.<>c__DisplayClass7_0`1.<SchedulePeriodic>b__0()
at System.Reactive.Concurrency.ConcurrencyAbstractionLayerImpl.PeriodicTimer.Tick(Object state)
at System.Threading.TimerQueueTimer.CallCallbackInContext(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.TimerQueueTimer.CallCallback()
at System.Threading.TimerQueueTimer.Fire()
at System.Threading.TimerQueue.FireNextTimers()
at System.Threading.TimerQueue.AppDomainTimerCallback()
We make an operator which replays an ordered sequence of events on a historical scheduler with the time moving according to a specified observable. 我们创建一个运算符,它在历史调度程序上重放一个有序的事件序列,并根据指定的observable移动时间。
public static IObservable<T> Playback<T>(
this IEnumerable<Timestamped<T>> enumerable,
IObservable<DateTimeOffset> ticks,
HistoricalScheduler scheduler = default(HistoricalScheduler)
)
{
return Observable.Create<T>(observer =>
{
scheduler = scheduler ?? new HistoricalScheduler();
//create enumerator of sequence - we're going to iterate through it manually
var enumerator = enumerable.GetEnumerator();
//set scheduler time for every incoming value of ticks
var timeD = ticks.Subscribe(scheduler.AdvanceTo);
//declare an iterator
Action scheduleNext = default(Action);
scheduleNext = () =>
{
//move
if (!enumerator.MoveNext())
{
//no more items
//sequence has completed
observer.OnCompleted();
return;
}
//current item of enumerable sequence
var current = enumerator.Current;
//schedule the item to run at the timestamp specified
scheduler.ScheduleAbsolute(current.Timestamp, () =>
{
//push the value forward
observer.OnNext(current.Value);
//schedule the next item
scheduleNext();
});
};
//start the process by scheduling the first item
scheduleNext();
//dispose the enumerator and subscription to ticks
return new CompositeDisposable(timeD, enumerator);
});
}
Porting your earlier example, 移植你之前的例子,
var enumerable = Enumerable.Range(0, 20000000);
var now = DateTimeOffset.Now;
var series = enumerable.Select(i => Timestamped.Create(i, now.AddSeconds(i)));
var ticks = Observable.Interval(TimeSpan.FromSeconds(1)).Select(i => now.AddSeconds(i));
series.Playback(ticks).Subscribe(Console.WriteLine);
We read through the enumerable to keep it lazy, and set the clock with a simple Interval
observable. 我们通过可枚举来读取它以保持惰性,并使用简单的Interval
observable设置时钟。 Reducing the interval causes it to playback faster. 缩短间隔会使其播放速度更快。
Phil, I think you will find problems regardless if you try to issue a notification every tick. 菲尔,我认为无论你是否尝试每次发出通知都会发现问题。 Rx just wont be able to keep up. Rx只是无法跟上。
I see what @asti is doing here, But I think it can made a touch simpler using what Paul already had ( IEnumerable<Timestamped<T>>
) 我看到@asti在这里做了什么,但我认为它可以使用Paul已经拥有的东西变得更简单( IEnumerable<Timestamped<T>>
)
public static IObservable<T> Playback<T>(
this IEnumerable<Timestamped<T>> enumerable,
IScheduler scheduler)
{
return Observable.Create<T>(observer =>
{
var enumerator = enumerable.GetEnumerator();
//declare a recursive function
Action<Action> scheduleNext = (self) =>
{
//move
if (!enumerator.MoveNext())
{
//no more items (or we have been disposed)
//sequence has completed
scheduler.Schedule(()=>observer.OnCompleted());
return;
}
//current item of enumerable sequence
var current = enumerator.Current;
//schedule the item to run at the timestamp specified
scheduler.Schedule(current.Timestamp, () =>
{
//push the value forward
observer.OnNext(current.Value);
//Recursively call self (via the scheduler API)
self();
});
};
//start the process by scheduling the recursive calls.
// return the scheduled handle to allow disposal.
var scheduledTask = scheduler.Schedule(scheduleNext);
return StableCompositeDisposable.Create(scheduledTask, enumerator);
});
}
This is also scheduler agnostic so will work with any scheduler. 这也是调度程序不可知的,因此可以与任何调度程序一起使用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.