稳定后将属性值发送到服务器

时间:2015-05-29 18:19:32

标签: c# wpf asynchronous client-server

我有一些看起来像这样的代码:

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似乎也不理想。还有更好的方法吗?

3 个答案:

答案 0 :(得分:1)

这种技术属于称为“事件合并”的逻辑类别。可能具有最小占用空间的实现如下:

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%的时间内工作,但很少,最后一次更改可能永远不会发送到服务器(由于线程之间缺少内存访问同步)

答案 1 :(得分:1)

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

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

答案 2 :(得分:0)

在考虑了各种选项(包括Rx扩展)之后,我选择了System.Threading.Timer类。当您调用其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();
}
塞特:

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

析构函数:

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

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