如何在没有任何副作用的情况下使用 Reactive Extensions (C#) 控制计时器?
How to control a timer using Reactive Extensions (C#) without any side-effects?
我正在尝试实现以下场景:
- 一个按钮被按下。
- 通过调用
GetInterval()
方法计算出一个interval
。
- 立即调用
DoSomething()
方法。
DoSomething()
方法将每隔 interval
毫秒重复调用一次,直到释放按钮。
- 再次按下按钮时,转到2。
可以这样实现:
Timer timer = new Timer();
timer.Tick += DoSomething();
//keyDown and keyUp are IObservables created from the button's events
keyDown.Do(_ =>
{
timer.Interval = GetInterval();
timer.Start();
DoSomething();
});
keyUp.Do(_ =>
{
timer.Stop();
});
我知道这段代码有一些问题,但没关系。我想要实现的是使用纯 Reactive Extensions 实现这个场景,没有任何外部计时器和副作用(除了调用 DoSomething()
方法)。
我知道 Observable.Timer,但我不知道如何在这种情况下使用它。
编辑: 我可以创建一个包含 interval
当前值的 IObservable<int>
。它可能会有所帮助。
keyDown
.SelectMany(_ =>
Observable.Interval(TimeSpan.FromMilliseconds(500))
.TakeUntil(keyUp))
.Subscribe(_ => DoSomething());
这将在每个按键事件上启动一个间隔。计时器将在发出 keyup 事件时完成。
我建议使用 Select
/Switch
的方法比使用 SelectMany
的方法更好。
一般来说,当使用 SelectMany
时,随着越来越多的基础订阅被创建,您最终可能会出现内存泄漏。由于 TakeUntil
,在这种情况下可能不会发生这种情况,但值得养成避免这种情况的习惯。
这是我的代码:
var subscription =
keyDown
.Select(kd =>
Observable
.Interval(TimeSpan.FromMilliseconds(500))
.TakeUntil(keyUp)
.StartWith(-1L))
.Switch()
.Subscribe(n => DoSomething());
当使用 Switch
时,您必须构造一个 IObservable<IObservable<T>>
来调用 .Switch()
,它采用 IObservable<IObservable<T>>
生成的最新 IObservable<T>
并将其展平与 SelectMany
类似,但前者处理了先前的可观察对象,而后者则没有。
我还包括了一个 StartWith
确保序列立即产生一个值。
我正在尝试实现以下场景:
- 一个按钮被按下。
- 通过调用
GetInterval()
方法计算出一个interval
。 - 立即调用
DoSomething()
方法。 DoSomething()
方法将每隔interval
毫秒重复调用一次,直到释放按钮。- 再次按下按钮时,转到2。
可以这样实现:
Timer timer = new Timer();
timer.Tick += DoSomething();
//keyDown and keyUp are IObservables created from the button's events
keyDown.Do(_ =>
{
timer.Interval = GetInterval();
timer.Start();
DoSomething();
});
keyUp.Do(_ =>
{
timer.Stop();
});
我知道这段代码有一些问题,但没关系。我想要实现的是使用纯 Reactive Extensions 实现这个场景,没有任何外部计时器和副作用(除了调用 DoSomething()
方法)。
我知道 Observable.Timer,但我不知道如何在这种情况下使用它。
编辑: 我可以创建一个包含 interval
当前值的 IObservable<int>
。它可能会有所帮助。
keyDown
.SelectMany(_ =>
Observable.Interval(TimeSpan.FromMilliseconds(500))
.TakeUntil(keyUp))
.Subscribe(_ => DoSomething());
这将在每个按键事件上启动一个间隔。计时器将在发出 keyup 事件时完成。
我建议使用 Select
/Switch
的方法比使用 SelectMany
的方法更好。
一般来说,当使用 SelectMany
时,随着越来越多的基础订阅被创建,您最终可能会出现内存泄漏。由于 TakeUntil
,在这种情况下可能不会发生这种情况,但值得养成避免这种情况的习惯。
这是我的代码:
var subscription =
keyDown
.Select(kd =>
Observable
.Interval(TimeSpan.FromMilliseconds(500))
.TakeUntil(keyUp)
.StartWith(-1L))
.Switch()
.Subscribe(n => DoSomething());
当使用 Switch
时,您必须构造一个 IObservable<IObservable<T>>
来调用 .Switch()
,它采用 IObservable<IObservable<T>>
生成的最新 IObservable<T>
并将其展平与 SelectMany
类似,但前者处理了先前的可观察对象,而后者则没有。
我还包括了一个 StartWith
确保序列立即产生一个值。