稳定后向服务器发送 属性 值

send property value to server after stabilization

我有一些代码如下所示:

class MyVM : VMBase {
  public MyVM(IMyServerProxy proxy) {
    _proxy = proxy;
    _proxy.ValueChanged += OnValueChangedFromServer;
  }
  private void OnValueChangedFromServer(int value){
    _value = value;
    RaisePropertyChanged(() => Value);
  }
  public int Value { // bound to slider
    get { return _value; }
    set {
      _value = value;
      // need something here to only send stable values to server
      _proxy.ModifyValue(value); // async
    }
  }
}

问题是这样的:值绑定到滑块控件。该滑块触发了很多变化。我不想将所有这些发送到服务器。我只想发送稳定值。本质上,我想在 Value setter 中插入一些东西,它只在 Value 整整一秒没有改变后才调用代理。 (我有一个次要的担心,即服务器会将过时的值更改路由回给我,但我认为如果我只是延迟发送到服务器,这将在很大程度上得到缓解。)

我研究了使用 Task.Delay 方法。但是,如果我取消延迟,它会抛出异常,并且在每次更新时构建一个新的 CancellationSource 似乎也不理想。有没有更好的方法?

此技术属于称为 "event coalescing" 的逻辑类别。可能占用空间最少的实现如下:

class MyVM : VMBase {
  private bool _isChangePending = false;

  public MyVM(IMyServerProxy proxy) {
    _proxy = proxy;
    _proxy.ValueChanged += OnValueChangedFromServer;
  }

  private void OnValueChangedFromServer(int value){
    _value = value;
    RaisePropertyChanged(() => Value);
  }

  public int Value { // bound to slider
    get { return _value; }
    set {
      lock(_isChangePending) {
        _value = value;
        // only send send "stable" values to server
        if (!_isChangePending){
          _isChangePending = true;
          System.Threading.ThreadPool.QueueUserWorkItem(delegate {
            this.SendAfterStabilize(value);
            }, null);
        }
      }
    }
  }

  private void SendAfterStabilize(int lastChangedValue) {
    while (true) {
      System.Threading.Thread.Sleep(1000);  // control coalescing delay here
      lock(_isChangePending) {
        if (_value == lastChangedValue) {
          _isChangePending = false;
          _proxy.ModifyValue(lastChangedValue); // async
          return;
        }
        else {
          lastChangedValue = _value;
        }
      }
    }
  }
}

请注意,lock() { } 块在技术上是必要的,以保证每次可能的最后一次更改(无论时间如何)总是在一秒后到达服务器。如果删除 lock() { } 块,代码在 99.99% 的时间仍然可以工作,但在极少数情况下,最后所做的更改可能永远不会发送到服务器(由于线程之间缺乏内存访问同步) .

在 .NET Framework 4.5 或更高版本中,您可以在 Slider 控件中使用 BindingBase.Delay Property

<Slider Value="{Binding Value, Delay=1000}"

在考虑了各种选项(包括 Rx 扩展)之后,我选择了 System.Threading.Timer class。 class 在您调用其 Change 方法时重新启动其计时器。

字段:

private readonly System.Threading.Timer _valueUpdater;
private bool _sendingValue;

在构造函数中:

_valueUpdater = new System.Threading.Timer(OnSendValue, null, System.Threading.Timeout.Infinite, 0);

回调:

private void OnSendValue(object state)
{
     _proxy.ModifyValue(_value).Wait();
    _sendingValue = false;
    if (_isDisposed)
        _valueUpdater.Dispose();
}

Setter:

_value = value;
_sendingValue = true;
_valueUpdater.Change(DelayMs, 0);

析构函数:

private bool _isDisposed;
public void Dispose()
{
    _isDisposed = true;

    if (!_sendingValue)
        _valueUpdater.Dispose();
...