我的目标是为“Type2”对象每5秒调用一次更新处理程序。 observable将每5秒生成多个值,但我想忽略在上次处理更新后5秒内发生的所有值。
我在这里问了这个问题: Throttle only if specific condition met
得到了很好的反馈。它让我使用Observable.Window来实现我的目标。我以为我有它工作,但事实证明,如果第一次更新在窗口关闭之前就出现(因此处理更新),然后一旦下一个窗口打开,另一个更新到达并且也被处理,它会产生不正确的输出我不希望它发生,因为它是在上次处理更新后的5秒内发生的。
以下是一些用于演示此问题的代码,稍微修改了链接中的代码:
var source = new Subject<Thing>();
var feed = source.Publish().RefCount();
var ofType1 = feed.Where(t => t.ActivationType == "Type1");
var ofType2 = feed
.Where(t => t.ActivationType == "Type2")
.Window(() =>
Observable.Timer(TimeSpan.FromSeconds(5))
.Do(t => Console.WriteLine("\nTICK: " + DateTime.Now.ToString("hh:mm:ss:fff"))))
.Select(x => x.Take(1))
.Merge()
.Do(t => Console.WriteLine("A new window opened " + DateTime.Now.ToString("hh:mm:ss:fff")));
var query = ofType1.Merge(ofType2);
query.Subscribe(t => Console.WriteLine("UPDATE: " + t.ID + " " + DateTime.Now.ToString("hh:mm:ss:fff")));
int msDelay = 3000;
Task task = Task.Factory
.StartNew(() => { Thread.Sleep(msDelay); })
.ContinueWith((Task starter) =>
{
while (running)
{
var thing = new Thing(); //Note that all Things are by default Type2
source.OnNext(thing);
Thread.Sleep(100);
}
}, TaskContinuationOptions.LongRunning);
Console.ReadLine();
因此,订阅完成,订阅完成后,Window中使用的Observable.Timer开始。用于产生值的while循环直到3000 ms延迟后才开始。
输出如下:
A new window opened 03:48:03:725
UPDATE: 1ac54fb3-f73d-4840-b4d8-95d4250ce65d 03:48:03:752
TICK: 03:48:05:714
A new window opened 03:48:05:754
UPDATE: 12d36e53-010f-4ccd-b9f8-2951b085f88c 03:48:05:754
TICK: 03:48:10:730
A new window opened 03:48:10:755
UPDATE: 25d84e72-94f9-4f50-83f4-14c1004c10fa 03:48:10:755
TICK: 03:48:15:738
A new window opened 03:48:15:755
UPDATE: 5f32b7d5-196f-445c-bf25-5c362b2fd6f0 03:48:15:755
TICK: 03:48:20:747
A new window opened 03:48:20:756
UPDATE: e3a3a30d-8031-41b5-b115-499dbe91aaf7 03:48:20:756
TICK: 03:48:25:755
A new window opened 03:48:25:756
UPDATE: 239fb25b-5135-463b-bf7e-5728ffa07f5c 03:48:25:756
正如您所看到的,第一个Type2更新在窗口打开时进入,因此会被处理。然后,2秒钟后,Window的计时器会打开,并打开一个新窗口。它立即处理下一个Type2更新,我不希望它做。之后它看起来正常工作(按照Window声明中的定义,每5秒更新一次)。
我是否可以使用某种方法或其他方法来确保每5秒(或我选择的任何时间范围)只有一次更新?
答案 0 :(得分:1)
我想我有一个解决方案,但首先我可以提出一些建议。 我认为问题中存在很多噪音,这使得很难找到真正的问题。
实际上你会问“我怎样才能获得一个值,然后静音至少5秒”。
Type1
代码令人分心。
序列的产生也是一种分心。
因此,让我们清理示例代码,看看我们是否可以看到树木:
首先,我不认为它的类型是完全相关的。
在您的示例中,我们从不推送Type1
,因此我们只使用整数。
它可能会更容易。
接下来,我们可以通过使用Observable.Timer而不是big loop + task + thread.sleep来清理创建。
现在我们有一个简单的起点:
var source = Observable.Timer(TimeSpan.FromSeconds(3),TimeSpan.FromMilliseconds(300), Scheduler.TaskPool);
var feed = source.Publish().RefCount();
所以我们的第一个问题是对您正在使用的Window重载的误解。 我认为你的期望是它会在推送第一个值时打开一个窗口。 事实并非如此。 定时器(即Observable.Timer(TimeSpan.FromSeconds(5)))在每次打开窗口时订阅,这最初是在订阅发生时,然后在窗口本身关闭时再次订阅。 因此,计时器立即启动,您将只获得第一个窗口的2秒值。
接下来,我会画出我的问题空间。我最喜欢的方法是用大理石图表。 他们在ASCII中不能很好地翻译,但我们可以尝试任何方式。
鉴于此输入序列:
//Seconds 1111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000
//Tenths 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
//
//source : ------------------------------0--1--2--3--4--5--6--7--8--9--0--1--2--3--4--5--6--7--8--9--0--1--2--3--4--5--6--7--8--9--0
这应该代表在3.0秒产生的值'0'。随后是3.3秒的'1'等。
现在问题空间有点清晰,我们可以在我们认为应该打开的窗口,应该关闭的位置以及下一个窗口应该打开的位置绘制。
让我们看看我们想要的东西。
这里我们添加一个Window1(W1),它在第一个值被推送时打开('1'@ 3.0s)。它在5秒后关闭。在这个窗口期间,我们希望在第一个值为1之后保持沉默。
窗口2(W2)应该在产生下一个值后打开,而不是在最后一个窗口关闭后立即打开(我认为?!)。 在这里,我们看到当在时间8.4秒推送值“17”时,这是打开的。
//Seconds 1111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000
//Tenths 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
//source : ------------------------------0--1--2--3--4--5--6--7--8--9--1--1--1--1--1--1--1--1--1--1--2--2--2--2--2--2--2--2--2--2--3--3--3--3--3--3--3--3--3--3--4--4--4--4-
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
//
//W1 : 0-------------------------------------------------|
//W2 : (17)----------------------------------------------|
//W3 : (34)------------------------>
//expected: ------------------------------0--------------------------------------------------1--------------------------------------------------3---------------------------
7 4
现在我们现在正在寻找什么值,我们可以构建一个查询。
我想出了这个。 我假设Feed实际上是Hot序列。 使用这个假设,我构造了一个重复的结构,从饲料中取1个值,并将5秒的静音连接到序列。 然后我添加Repeat运算符,只有在5秒的静音失效后才会重新订阅Feed。
public static IObservable<T> Silencer<T>(this IObservable<T> source, TimeSpan minSilencePeriod)
{
return source.Take(1)
.Concat(Observable.Empty<T>().Delay(minSilencePeriod))
.Repeat();
}
这确实产生了预期的值0,17,51等。
现在将此应用于原始问题的代码(清理一些东西)
void Main()
{
var source = Observable.Timer(TimeSpan.FromSeconds(3),TimeSpan.FromMilliseconds(300), Scheduler.TaskPool).Select(_=>new Thing());
var feed = source.Publish().RefCount();
var ofType1 = feed.Where(t => t.ActivationType == "Type1");
var ofType2 = feed
.Where(t => t.ActivationType == "Type2")
.Silencer(TimeSpan.FromSeconds(5));
var query = ofType1.Merge(ofType2);
var subscription = query.Subscribe(t => Console.WriteLine("UPDATE: " + t.ID + " " + DateTime.Now.ToString("hh:mm:ss:fff")));
Console.ReadLine();
subscription.Dispose();
}
我们看到输出的值至少相隔5秒
UPDATE: 3f0fc6f3-8a5a-476f-9661-b7330ab77877 09:14:04:725
UPDATE: fc8f0025-7a79-4329-8164-b8b421ad5865 09:14:09:817
UPDATE: ad739a71-885e-4d5b-a352-2302df0a4d87 09:14:14:925