C# 如何划分(分组)流,并在一定时间段内监控Rx中元素的缺失?

C# 如何划分(分组)流,并在一定时间段内监控Rx中元素的缺失?,c#,events,system.reactive,reactive-programming,C#,Events,System.reactive,Reactive Programming,在前几天,我一直在尝试编写一个Rx查询,以处理来自源的事件流,并检查是否缺少一些ID。缺勤的定义应确保存在一系列时间窗口(例如,从9:00到17:00的所有日子),在此期间,流中最多应有20分钟没有ID。为了使事情进一步复杂化,缺席时间应根据身份证确定。 例如,假设三种类型的事件A、B和C出现在组合的事件流中(A、A、B、C、A、C、B等等),可以定义为 A每天从9:00到10:00监控事件,最多10分钟不发生事件 B每天从9:00到11:00监控事件,最多不发生事件5分钟 C每天从12:00

在前几天,我一直在尝试编写一个Rx查询,以处理来自源的事件流,并检查是否缺少一些ID。缺勤的定义应确保存在一系列时间窗口(例如,从9:00到17:00的所有日子),在此期间,流中最多应有20分钟没有ID。为了使事情进一步复杂化,缺席时间应根据身份证确定。 例如,假设三种类型的事件A、B和C出现在组合的事件流中(A、A、B、C、A、C、B等等),可以定义为

  • A每天从9:00到10:00监控事件,最多10分钟不发生事件
  • B每天从9:00到11:00监控事件,最多不发生事件5分钟
  • C每天从12:00到15:00监控事件,最大无事件时间为30分钟
我想我需要首先按照GroupBy将流划分成单独的事件,然后使用缺席规则处理产生的单独流。我已经仔细考虑过了(非常感谢Dave),我有一些工作代码来生成规则和进行缺勤检查,但我很难,例如,如何将其与分组相结合

因此,在没有进一步发言的情况下,迄今为止被黑客攻击的代码:

//Some sample data bits representing the events.
public class FakeData
{
    public int Id { get; set; }

    public string SomeData { get; set; }
}

//Note the Now part in DateTime to zero the clock time and have only the date. The  purpose is to create start-end pairs of times, e.g. 9:00-17:00.
//The alarm start and end time points should match themselves pairwise, could be pairs of values...
var maxDate = DateTime.Now.Date.AddHours(17).AddMinutes(0).AddSeconds(0).AddDays(14);
var startDate = DateTime.Now.Date.AddHours(9).AddMinutes(0).AddSeconds(0);
var alarmStartPeriods =   Enumerable.Range(0, 1 + (maxDate - startDate).Days).Select(d => new DateTimeOffset(startDate.AddDays(d))).ToList();
var alarmEndPeriods = Enumerable.Range(0, 1 + (maxDate - startDate).Days).Select(d => new DateTimeOffset(startDate.AddDays(d)).AddHours(5)).ToList();

还有一个不用分组就可以进行缺勤检查的查询,这是我的难点之一已编辑-改进了代码,简化了
SelectMany
以使用
TakeLast

我写了一篇关于检测断开连接的客户机的文章——如果您用下面的alarmInterval之类的函数替换帖子中的timeToHold变量,以根据客户机ID获取节流时间跨度,那么这篇文章同样适用于您的场景

e、 g:

//idStream是一个可观察到的ID输入流
//alarmInterval是一个函数,它获取给定ID的间隔
var idAlarmStream=idStream
.GroupByUntil(key=>key,grp=>grp.Throttle(alarmInterval(grp.key)))
.SelectMany(grp=>grp.TakeLast(1));
这为您提供了持续监控的基本功能,而无需查看活动监控周期

为了获得监视器窗口的功能,我会将事情扭转过来,并过滤上面的输出,其中会检查发出的ID是否落在它的监视时间窗口中。这使得处理不断变化的监视周期变得更容易

您可以通过将每个监视窗口转换为一个流并将它们与警报流相结合来做一些更有趣的事情,但我不相信额外复杂性的好处

alarmInterval函数还将为您提供动态报警间隔元素,因为它可以返回新值,但这些值仅在该ID的报警关闭后生效,从而结束其当前组

---在这里开始一些理论---

为了让这个团队充满活力,你必须以某种方式结束团队——你可以通过几种方式来完成

一种方法是使用Select将idStream投影到一个自定义类型的流中,该流包含ID和一个全局计数器值。为该类型提供一个适当的相等实现,以便它能够与GroupBy一起正常工作


现在,每次更改报警间隔时,都要更改计数器。这将导致为每个ID创建新的组。然后,您可以在最终筛选器中添加额外的检查,以确保输出事件具有最新的计数器值。

Hmm,看起来像一个计划。比如说,如果idStream是用Observable.FromEventPattern创建的,您能帮我一把吗?告诉我如何进行查询?我不确定在SelectMany中或之前我需要如何处理它。顺便说一句,您描述的过滤策略就是我使用过的“计时器和事件参数是否具有ID?然后在开始时调用Observable.FromEventPattern,并使用Select将结果IObservable投影到ID+计数器类型,然后使用GroupByUntil等继续上述操作。我写得有点太早了,我想知道的是,在SelectMany中没有可调用的IgnoreElements。只有普通对象类型,例如int或XXXEventArgs。我想知道现在发生了什么。看起来(除非我做错了什么),即使我使用了Subject或IObserable,这个问题仍然存在。好吧,看起来缺少了一个括号。它应该读取grp.Throttle(alarmInterval(grp.Key)),也就是说,有三个关闭的节流阀,而不是两个。我又向前迈进了…没问题!我写了几行关于这个小片段和你的博客文章的问题。谢谢
dataSource = from n in Observable.Interval(TimeSpan.FromMilliseconds(100))
             select new FakeData
             {
                 Id = new Random().Next(1, 5),
                 SomeData = DateTimeOffset.Now.ToString()
             };

var startPointOfTimeChanges = alarmStartPeriods.ToObservable();
var endPointOfTimeChanges = alarmEndPeriods.ToObservable();
var durations = startPointOfTimeChanges.CombineLatest(endPointOfTimeChanges, (start, end) => new { start, end });
var maximumInactivityTimeBeforeAlarmSignal =  TimeSpan.FromMilliseconds(250);

timer = (from duration in durations
         select (from _ in Observable.Timer(DateTime.Now)
                 from x in dataSource.Throttle(maximumInactivityTimeBeforeAlarmSignal).TakeUntil(duration.end)
                 select x)).Switch();

timer.Subscribe(x => Debug.WriteLine(x.SomeData));
System.Threading.Timers
ConcurrentDictionary
// idStream is an IObservable<int> of the input stream of IDs
// alarmInterval is a Func<int, TimeSpan> that gets the interval given the ID
var idAlarmStream = idStream
    .GroupByUntil(key => key, grp => grp.Throttle(alarmInterval(grp.Key)))
    .SelectMany(grp => grp.TakeLast(1));