[英]Throttle Rx.Observable without skipping values
如果其他人跟随太快,则Throttle
方法会从可观察序列中跳过值。 但我需要一种方法来延迟它们。 也就是说,我需要在项目之间设置最小延迟,而不是跳过任何项目 。
实际例子:有一个Web服务可以接受不超过一秒的请求; 有一个用户可以单独或批量添加请求。 没有Rx,我将创建一个列表和一个计时器。 当用户添加请求时,我会将它们添加到列表中。 在计时器事件中,我将检查列表是否为空。 如果不是,我将发送请求并删除相应的项目。 随着锁和所有的东西。 现在,使用Rx,我可以创建Subject
,在用户添加请求时添加项目。 但我需要一种方法来确保Web服务不会因应用延迟而泛滥。
我是Rx的新手,所以也许我错过了一些明显的东西。
使用EventLoopScheduler
有一种相当简单的方法可以做你想做的EventLoopScheduler
。
我从一个observable开始,每隔0到3秒就会随机产生一次值。
var rnd = new Random();
var xs =
Observable
.Generate(
0,
x => x < 20,
x => x + 1,
x => x,
x => TimeSpan.FromSeconds(rnd.NextDouble() * 3.0));
现在,要立即生成此输出值,除非最后一个值在一秒钟之内,我这样做:
var ys =
Observable.Create<int>(o =>
{
var els = new EventLoopScheduler();
return xs
.ObserveOn(els)
.Do(x => els.Schedule(() => Thread.Sleep(1000)))
.Subscribe(o);
});
这有效地观察了EventLoopScheduler
上的EventLoopScheduler
,然后在每个OnNext
之后将其置于休眠状态1秒,这样它只能在唤醒之后开始下一个OnNext
。
我测试它使用此代码:
ys
.Timestamp()
.Select(x => x.Timestamp.Second + (double)x.Timestamp.Millisecond/1000.0)
.Subscribe(x => Console.WriteLine(x));
我希望这有帮助。
我想建议使用Observable.Zip
的方法:
// Incoming requests
var requests = new[] {1, 2, 3, 4, 5}.ToObservable();
// defines the frequency of the incoming requests
// This is the way to emulate flood of incoming requests.
// Which, by the way, uses the same approach that will be used in the solution
var requestsTimer = Observable.Interval(TimeSpan.FromSeconds(0.1));
var incomingRequests = Observable.Zip(requests, requestsTimer, (number, time) => {return number;});
incomingRequests.Subscribe((number) =>
{
Console.WriteLine($"Request received: {number}");
});
// This the minimum interval at which we want to process the incoming requests
var processingTimeInterval = Observable.Interval(TimeSpan.FromSeconds(1));
// Zipping incoming requests with the interval
var requestsToProcess = Observable.Zip(incomingRequests, processingTimeInterval, (data, time) => {return data;});
requestsToProcess.Subscribe((number) =>
{
Console.WriteLine($"Request processed: {number}");
});
一个简单的扩展方法怎么样:
public static IObservable<T> StepInterval<T>(this IObservable<T> source, TimeSpan minDelay)
{
return source.Select(x =>
Observable.Empty<T>()
.Delay(minDelay)
.StartWith(x)
).Concat();
}
用法:
var bufferedSource = source.StepInterval(TimeSpan.FromSeconds(1));
我正在玩这个并发现.Zip(如前所述)是最简单的方法:
var stream = "ThisFastObservable".ToObservable();
var slowStream =
stream.Zip(
Observable.Interval(TimeSpan.FromSeconds(1)), //Time delay
(x, y) => x); // We just care about the original stream value (x), not the interval ticks (y)
slowStream.TimeInterval().Subscribe(x => Console.WriteLine($"{x.Value} arrived after {x.Interval}"));
输出:
T arrived after 00:00:01.0393840
h arrived after 00:00:00.9787150
i arrived after 00:00:01.0080400
s arrived after 00:00:00.9963000
F arrived after 00:00:01.0002530
a arrived after 00:00:01.0003770
s arrived after 00:00:00.9963710
t arrived after 00:00:01.0026450
O arrived after 00:00:00.9995360
b arrived after 00:00:01.0014620
s arrived after 00:00:00.9993100
e arrived after 00:00:00.9972710
r arrived after 00:00:01.0001240
v arrived after 00:00:01.0016600
a arrived after 00:00:00.9981140
b arrived after 00:00:01.0033980
l arrived after 00:00:00.9992570
e arrived after 00:00:01.0003520
如何使用可观察的计时器从阻塞队列中获取? 下面的代码未经测试,但应该让您了解我的意思......
//assuming somewhere there is
BlockingCollection<MyWebServiceRequestData> workQueue = ...
Observable
.Timer(new TimeSpan(0,0,1), new EventLoopScheduler())
.Do(i => myWebService.Send(workQueue.Take()));
// Then just add items to the queue using workQueue.Add(...)
考虑使用缓冲区。
http://msdn.microsoft.com/en-us/library/hh212130(v=vs.103).aspx
正如名称所示,它在可观察的事件流中缓冲元素而不“丢弃”它们中的任何一个。
-G
.Buffer(TimeSpan.FromSeconds(0.2)).Where(i => i.Any())
.Subscribe(buffer =>
{
foreach(var item in buffer) Console.WriteLine(item)
});
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.