C# 反应式编程使时间序列值正常化
我有一个远程程序,通过套接字连接每隔10毫秒发送一次更新的测量值。在我的客户机程序中,我将这个套接字包装在一个可观察的容器中,该容器生成了这些测量值。对于我的用例来说,测量值以10毫秒的间隔到达是很重要的。当然,这种情况不会发生,因为网络延迟会使每条消息的到达时间稍早或稍晚 所以基本上我在我的远程pc上有一个程序,它通过套接字连接发送数据C# 反应式编程使时间序列值正常化,c#,system.reactive,reactive-programming,C#,System.reactive,Reactive Programming,我有一个远程程序,通过套接字连接每隔10毫秒发送一次更新的测量值。在我的客户机程序中,我将这个套接字包装在一个可观察的容器中,该容器生成了这些测量值。对于我的用例来说,测量值以10毫秒的间隔到达是很重要的。当然,这种情况不会发生,因为网络延迟会使每条消息的到达时间稍早或稍晚 所以基本上我在我的远程pc上有一个程序,它通过套接字连接发送数据 --为10毫秒 o--o--o--o--o--o--o--o--o--o--... 由于网络延迟,在我的客户机上会变成这样 o-o---o-o--o---o
--
为10毫秒
o--o--o--o--o--o--o--o--o--o--...
由于网络延迟,在我的客户机上会变成这样
o-o---o-o--o---o--o-o--o-o-...
现在在我的observable中,我想“归一化”它,这样它会再次每10毫秒发出一个值
--o--o--o--o--o--o--o--o--o--o...
当然,这意味着我必须引入一个缓冲时间,它将存储值并以10毫秒的间隔发出它们。有什么办法可以做到这一点吗
下面是一些测试代码,它们将按照我上面描述的方式发出事件
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Microsoft.Reactive.Testing;
public class Program
{
protected static event EventHandler<EventArgs> CancelEvent;
private static Random random = new Random();
private static double GetRandomNumber(double minimum, double maximum)
{
return random.NextDouble() * (maximum - minimum) + minimum;
}
public static void Main()
{
var completed = false;
var scheduler = new TestScheduler();
var observable = Observable
.Interval(TimeSpan.FromMilliseconds(7.0), scheduler)
.SelectMany(e => Observable
.Return(e, scheduler)
.Delay(TimeSpan.FromMilliseconds(GetRandomNumber(0.0, 6.0)), scheduler)
)
.TimeInterval(scheduler)
.Select(t => t.Interval.Milliseconds);
var fromEvent = Observable.FromEventPattern<EventArgs>(
p => CancelEvent += p,
p => CancelEvent -= p,
scheduler
);
var cancellable = observable.TakeUntil(fromEvent);
var results = new List<int>();
using (cancellable.Subscribe(
results.Add,
e => { throw new Exception("No exception is planned! {0}", e); },
() => { completed = true; })
)
{
scheduler.AdvanceBy(TimeSpan.FromSeconds(3.5).Ticks);
CancelEvent(null, new EventArgs());
scheduler.AdvanceBy(TimeSpan.FromSeconds(3).Ticks);
}
Console.WriteLine("Have I completed indeed? {0}", completed);
Console.WriteLine("What emit time deltas been registered before cancellation?\n\t{0}", string.Join("ms\n\t", results));
}
}
使用系统;
使用System.Collections.Generic;
使用系统反应性一次性用品;
使用System.Reactive.Linq;
使用System.Threading.Tasks;
使用Microsoft.Reactive.Testing;
公共课程
{
受保护的静态事件EventHandler CancelEvent;
私有静态随机=新随机();
私有静态双GetRandomNumber(最小值加倍,最大值加倍)
{
返回random.NextDouble()*(最大值-最小值)+最小值;
}
公共静态void Main()
{
var completed=假;
var scheduler=newtestscheduler();
var可观测=可观测
.Interval(时间跨度从毫秒(7.0),调度程序)
.选择多个(e=>可观察
.返回(e,调度程序)
.Delay(TimeSpan.From毫秒(GetRandomNumber(0.0,6.0)),调度程序)
)
.时间间隔(调度程序)
.Select(t=>t.Interval.millities);
var fromEvent=Observable.FromEventPattern(
p=>CancelEvent+=p,
p=>CancelEvent-=p,
调度程序
);
var Cancelable=可观测的。TakeUntil(fromEvent);
var results=新列表();
使用(cancelable.Subscribe)(
结果。添加,
e=>{抛出新异常(“没有计划异常!{0}”,e);},
()=>{completed=true;})
)
{
调度程序.AdvanceBy(TimeSpan.FromSeconds(3.5).Ticks);
CancelEvent(null,新的EventArgs());
调度程序.AdvanceBy(TimeSpan.FromSeconds(3).Ticks);
}
WriteLine(“我确实完成了吗?{0}”,已完成);
Console.WriteLine(“取消之前注册了什么发射时间增量?\n\t{0}”,string.Join(“ms\n\t”,results));
}
}
这在理论上类似于
该解决方案如下所示:
var source = new Subject<double>();
var bufferTime = TimeSpan.FromMilliseconds(100);
var normalizedSource = source
.Delay(bufferTime)
.Drain(x => Observable.Empty<int>().Delay(TimeSpan.FromMilliseconds(10)));
然而,我认为你会遇到10毫秒限定符的问题。这时间安排得太少了。如果我没记错的话,任何小于15毫秒的延迟都会被调度程序忽略,并立即触发。有鉴于此,即使您使用了更大的间隔(我尝试了100毫秒),由于操作系统上下文切换等原因,您也会得到一些差异。您可以简单地
.Zip
使用.interval(TimeSpan.frommilluses(10.0))
?另外,请记住,Windows不是一个实时操作系统,无论您做什么,都无法将计时降至10毫秒。只有在时间间隔始终低于源代码时,Zip才会工作。如果有一个很长的延迟,然后是一个突发,那么它们会同时聚集在一起。
public static class ObservableDrainExtensions
{
public static IObservable<TOut> Drain<TSource, TOut>(this IObservable<TSource> source,
Func<TSource, IObservable<TOut>> selector)
{
return Observable.Defer(() =>
{
BehaviorSubject<Unit> queue = new BehaviorSubject<Unit>(new Unit());
return source
.Zip(queue, (v, q) => v)
.SelectMany(v => selector(v)
.Do(_ => { }, () => queue.OnNext(new Unit()))
);
});
}
}