我需要实现一个事件处理,即在没有新事件到达某段时间时延迟完成。 (我必须在文本缓冲区更改时排队解析任务,但我不想在用户仍在键入时启动解析。)
我是RX的新手,但据我所知,我需要BufferWithTime和Timeout方法的组合。我想这是这样的:它会缓冲事件,直到在后续事件之间的指定时间段内定期接收事件。如果事件流中存在间隙(长于时间跨度),则应该返回传播到目前为止缓冲的事件。
看看如何实现Buffer和Timeout,我可以实现我的BufferWithTimeout方法(如果每个人都有一个,请与我分享),但我想知道这是否可以通过结合现有方法来实现。有什么想法吗?
答案 0 :(得分:11)
这是一个相当古老的问题,但我确实认为以下答案值得一提,因为所有其他解决方案都迫使用户手动订阅,跟踪更改等。
我提供以下作为“Rx-y”解决方案。
var buffers = source
.GroupByUntil(
// yes. yes. all items belong to the same group.
x => true,
g => Observable.Amb<int>(
// close the group after 5 seconds of inactivity
g.Throttle(TimeSpan.FromSeconds(5)),
// close the group after 10 items
g.Skip(9)
))
// Turn those groups into buffers
.SelectMany(x => x.ToArray());
基本上,源是窗口化的,直到根据最新窗口定义的一些观察。创建一个新窗口(分组可观察),我们使用该窗口确定窗口何时关闭。在这种情况下,我在5秒不活动或最大长度为10(9 + 1)后关闭窗口。
答案 1 :(得分:3)
我认为 BufferWithTime
就是你所追求的。
内置任何东西,但这样的事情应该有效:
注意:如果源发生错误,则不刷新缓冲区。这与BufferWith*
public static IObservable<TSource[]> BufferWithTimeout<TSource>(
this IObservable<TSource> source, TimeSpan timeout)
{
return source.BufferWithTimeout(timeout, Scheduler.TaskPool);
}
public static IObservable<TSource[]> BufferWithTimeout<TSource>(
this IObservable<TSource> source, TimeSpan timeout, IScheduler scheduler)
{
return Observable.CreateWithDisposable<TSource[]>(observer =>
{
object lockObject = new object();
List<TSource> buffer = new List<TSource>();
MutableDisposable timeoutDisposable = new MutableDisposable();
Action flushBuffer = () =>
{
TSource[] values;
lock(lockObject)
{
values = buffer.ToArray();
buffer.Clear();
}
observer.OnNext(values);
};
var sourceSubscription = source.Subscribe(
value =>
{
lock(lockObject)
{
buffer.Add(value);
}
timeoutDisposable.Disposable =
scheduler.Schedule(flushBuffer, timeout);
},
observer.OnError,
() =>
{
flushBuffer();
observer.OnCompleted();
});
return new CompositeDisposable(sourceSubscription, timeoutDisposable);
});
}
答案 2 :(得分:2)
除了Richard Szalay的回答,我刚刚从最新的rx版本开始研究新的Window
运算符。它有点“解决你的问题,因为你可以'缓冲超时',即在一个持续到超时的时间窗口内获取输出,但不是接收结果作为IEnumerable你实际得到它们作为IObservable。
以下是我的意思的快速示例:
private void SetupStream()
{
var inputStream = Observable.FromEvent<MouseButtonEventHandler, MouseButtonEventArgs>(
h => new MouseButtonEventHandler(h),
h => MouseDown += h,
h => MouseDown -= h);
var timeout = inputStream.Select(evt => Observable.Timer(TimeSpan.FromSeconds(10), Scheduler.Dispatcher))
.Switch();
inputStream.Window(() => timeout)
.Subscribe(OnWindowOpen);
}
private void OnWindowOpen(IObservable<IEvent<MouseButtonEventArgs>> window)
{
Trace.WriteLine(string.Format("Window open"));
var buffer = new List<IEvent<MouseButtonEventArgs>>();
window.Subscribe(click =>
{
Trace.WriteLine(string.Format("Click"));
buffer.Add(click);
}, () => ProcessEvents(buffer));
}
private void ProcessEvents(IEnumerable<IEvent<MouseButtonEventArgs>> clicks)
{
Trace.WriteLine(string.Format("Window closed"));
//...
}
每次打开窗口时,您会在它们进入时收到所有事件,将它们存储在缓冲区中并在窗口完成时进行处理(实际上在下一个窗口打开时会发生)。
不确定理查德是否会改变他的例子以使用Window
现在它可用但是认为可能值得提出作为替代。
答案 3 :(得分:1)
如果您只需要在用户停止键入一段时间后运行操作,并且不一定需要中间事件,那么Throttle
就是您所追求的操作。检查here以获取该场景中其用法的示例。