如何使用反应式扩展 (C#) 控制计时器,而不会产生任何副作用

本文关键字:副作用 任何 计时器 扩展 反应式 何使用 控制 | 更新日期: 2023-09-27 18:19:33

我正在尝试实现以下场景:

  1. 按下一个按钮。
  2. 通过调用 GetInterval() 方法计算interval
  3. 立即调用 DoSomething() 方法。
  4. DoSomething() 方法将每 interval 毫秒重复调用一次,直到释放按钮。
  5. 再次按下按钮时,转到 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();
});

我知道这段代码有一些问题,但没关系。我试图实现的是使用纯反应式扩展实现此方案,没有任何外部计时器和副作用(除了调用 DoSomething() 方法(。

我知道 Observable.Timer,但我不知道如何在这种情况下使用它。

编辑:我可以创建一个包含interval当前值的IObservable<int>。它可能会有所帮助。

如何使用反应式扩展 (C#) 控制计时器,而不会产生任何副作用

我将建议使用 Select/Switch 的方法比使用 SelectMany 的方法更好。

通常,使用 SelectMany 时,随着创建越来越多的基础订阅,最终可能会出现内存泄漏。由于 TakeUntil ,在这种情况下可能不会发生这种情况,但值得养成避免这种情况的习惯。

这是我的代码:

var subscription =
    keyDown
        .Select(kd =>
            Observable
                .Interval(TimeSpan.FromMilliseconds(500))
                .TakeUntil(keyUp)
                .StartWith(-1L))
        .Switch()
        .Subscribe(n => DoSomething());

使用 Switch 时,您必须构造一个要调用.Switch()IObservable<IObservable<T>>,该获取IObservable<IObservable<T>>产生的最新IObservable<T>并将其展平,类似于SelectMany但前者会释放前一个可观察量,而后者则不会。

我还包含一个StartWith,可确保序列立即生成值。

keyDown
   .SelectMany(_ => 
       Observable.Interval(TimeSpan.FromMilliseconds(500))
          .TakeUntil(keyUp))
   .Subscribe(_ => DoSomething());

这将在每个键按下事件上启动一个间隔。计时器将在发出键控事件时完成。