我一直在尝试构建一个可以配置为使用复合规则跳闸的断路器,如:
因此认为最好的方法是使用滑动窗口(或者任何你想要调用它们的时间缓冲区)。
根据我在网上找到的内容并拼凑自己,我写了这个简单的控制台应用程序:
static IEnumerable<ConsoleKeyInfo> KeyPresses()
{
ConsoleKeyInfo key;
do
{
key = Console.ReadKey();
yield return key;
} while (key.Key != ConsoleKey.Escape);
}
static IObservable<int> TimeLimitedThreshold<T>(IObservable<T> source, TimeSpan timeLimit, int threshold)
{
return source.Window(source, _ => Observable.Timer(timeLimit))
.Select(x => x.Count())
.Merge()
.Where(count => count >= threshold)
.Take(1);
}
static void Main(string[] args)
{
Console.WriteLine("Starting");
var timeLimit = TimeSpan.FromSeconds(5);
const int threshold = 3;
var keys = KeyPresses().ToObservable(Scheduler.Default).Publish().RefCount();
var thresholdHit = TimeLimitedThreshold(keys, timeLimit, threshold);
thresholdHit.Subscribe(count => Console.WriteLine("THRESHOLD BREACHED! Count is: {0}", count));
// block the main thread so we don't terminate
keys.Where(key => key.Key == ConsoleKey.Escape).FirstAsync().Wait();
Console.WriteLine("Finished");
}
(如果我把它放在gist或pastebin而不是问题中,请说出来)
现在这似乎做了我想要的,如果我在5秒内按任意键3次或更多次“THRESHOLD BREACHED !!!”打印一次,没有其他事情发生。
我的问题是:
答案 0 :(得分:2)
我可能会通过以下方式编写阈值函数,利用Timestamp
组合子。
public static IObservable<U> TimeLimitedThreshold
<T,U>
( this IObservable<T> source
, int count
, TimeSpan timeSpan
, Func<IList<T>,U> selector
, IScheduler scheduler = null
)
{
var tmp = scheduler == null
? source.Timestamp()
: source.Timestamp(scheduler);
return tmp
.Buffer(count, 1).Where(b=>b.Count==count)
.Select(b => new { b, span = b.Last().Timestamp - b.First().Timestamp })
.Where(o => o.span <= timeSpan)
.Select(o => selector(o.b.Select(ts=>ts.Value).ToList()));
}
作为触发器触发时的一个额外的便利,满足触发器的完整缓冲区被提供给选择器函数。
例如
var keys = KeyPresses().ToObservable(Scheduler.Default).Publish().RefCount();
IObservable<string> fastKeySequences = keys.TimeLimitedThreshHold
( 3
, TimeSpan.FromSeconds(5)
, keys => String.Join("", keys)
);
提供了额外的IScheduler
参数,因为Timestamp
方法有一个额外的重载,需要一个。如果您想要一个不根据内部时钟跟踪时间的自定义调度程序,这可能很有用。出于测试目的,使用historical scheduler可能很有用,然后您需要额外的过载。
这是一个完全有效的测试,显示了调度的使用。 (使用XUnit和FluentAssertions作为Should()。Be(..))
public class TimeLimitedThresholdSpec : ReactiveTest
{
TestScheduler _Scheduler = new TestScheduler();
[Fact]
public void ShouldWork()
{
var o = _Scheduler.CreateColdObservable
( OnNext(100, "A")
, OnNext(200, "B")
, OnNext(250, "C")
, OnNext(255, "D")
, OnNext(258, "E")
, OnNext(600, "F")
);
var fixture = o
.TimeLimitedThreshold
(3
, TimeSpan.FromTicks(20)
, b => String.Join("", b)
, _Scheduler
);
var actual = _Scheduler
.Start(()=>fixture, created:0, subscribed:1, disposed:1000);
actual.Messages.Count.Should().Be(1);
actual.Messages[0].Value.Value.Should().Be("CDE");
}
}
订阅并采用以下方式
IDisposable subscription = fastKeySequences.Subscribe(s=>Console.WriteLine(s));
当您想要取消订阅(清理内存和资源)时,您将处理订阅。简单地
subscription.Dispose()
答案 1 :(得分:2)
这是一种替代方法,它使用单个延迟来支持缓冲区和定时器。它没有给你发送事件 - 它只是在有违规时发出信号 - 但是它使用的内存较少,因为它没有太多。
public static class ObservableExtensions
{
public static IObservable<Unit> TimeLimitedThreshold<TSource>(
this IObservable<TSource> source,
long threshold,
TimeSpan timeLimit,
IScheduler s)
{
var events = source.Publish().RefCount();
var count = events.Select(_ => 1)
.Merge(events.Select(_ => -1)
.Delay(timeLimit, s));
return count.Scan((x,y) => x + y)
.Where(c => c == threshold)
.Select(_ => Unit.Default);
}
}
Publish().RefCount()
用于避免订阅多个来源。该查询将所有事件投影到1
,并将延迟的事件流投影到-1
,然后生成一个运行总计。如果运行总计达到阈值,我们发出一个信号(Unit.Default
是Rx类型,表示没有有效负载的事件)。这是一个测试(只在带有nuget rx-testing
的LINQPad中运行):
void Main()
{
var s = new TestScheduler();
var source = s.CreateColdObservable(
new Recorded<Notification<int>>(100, Notification.CreateOnNext(1)),
new Recorded<Notification<int>>(200, Notification.CreateOnNext(2)),
new Recorded<Notification<int>>(300, Notification.CreateOnNext(3)),
new Recorded<Notification<int>>(330, Notification.CreateOnNext(4)));
var results = s.CreateObserver<Unit>();
source.TimeLimitedThreshold(
2,
TimeSpan.FromTicks(30),
s).Subscribe(results);
s.Start();
ReactiveAssert.AssertEqual(
results.Messages,
new List<Recorded<Notification<Unit>>> {
new Recorded<Notification<Unit>>(
330, Notification.CreateOnNext(Unit.Default))
});
}
在Matthew Finlay的观察结果显示上面的内容也会随着阈值的传递而消失&#34;在下降的路上#34;我添加了这个版本,只检查正向的阈值交叉:< / p>
public static class ObservableExtensions
{
public static IObservable<Unit> TimeLimitedThreshold<TSource>(
this IObservable<TSource> source,
long threshold,
TimeSpan timeLimit,
IScheduler s)
{
var events = source.Publish().RefCount();
var count = events.Select(_ => 1)
.Merge(events.Select(_ => -1)
.Delay(timeLimit, s));
return count.Scan((x,y) => x + y)
.Scan(new { Current = 0, Last = 0},
(x,y) => new { Current = y, Last = x.Current })
.Where(c => c.Current == threshold && c.Last < threshold)
.Select(_ => Unit.Default);
}
}