使用信号量的异步切换按钮可以避免服务负载

时间:2014-02-12 14:19:01

标签: c# xaml asynchronous windows-phone-8 binding

我有一个带有蒙皮切换按钮的Windows Phone客户端,它充当了“收藏夹”按钮。然后,checked属性与ViewModel(标准MVVM模式)双向绑定。

<ToggleButton IsChecked="{Binding DataContext.IsFavouriteUser, ElementName=PageRoot, Mode=TwoWay}">

当更改绑定布尔值时,我想启动对服务的异步网络调用。

    public bool IsFavouriteUser
    {
        get { return _isFavouriteUser; }
        set
        {
            if (SetProperty(ref _isFavouriteUser, value))
            {
                // Dispatches the state change to a RESTful service call
                // in a background thread.
                SetFavouriteState();
            }
        }
    }

如果用户多次按下该按钮,则可以进行许多添加/删除异步服务调用 - 假设这些需要2秒钟来进行网络往返和服务处理。

过去我使用过类似的东西:

    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1);

    // I would probably dispatch this call to a background thread in the real client
    public async Task<bool> SetFavouriteState()
    {
        try
        {
            await _semaphore.WaitAsync();

            bool result;

            if (IsFavouriteUser)
            {
                result = await ServiceClient.AddAsync(x);
            }
            else
            {
                result = await ServiceClient.RemoveAsync(x);
            }

            return result;
        }
        catch
        {
            // I wouldn't use an empty catch in production code
            return false;
        }
        finally
        {
            _semaphore.Release();
        }
    }

然而,这可能无休止地排队用户输入;而该服务仅对最新的用户事件感兴趣 - 开启或关闭 - 并且UI应保持对用户输入的响应。

  • 如果用户反复点击按钮,确保客户端不发送“添加/删除/添加/删除”的最佳方法是什么。即我想忽略中间的两个事件,只发送“添加,等待响应完成,删除”。
  • 是否有更好的方法以异步方式绑定到此布尔属性?
  • 锁定模型的最佳方法是什么,以便在此环境中只有一个请求在任何时候都在进行?
  • 在我们等待呼叫发生(并且可能失败)时,告知用户发生了什么事情的最佳方法是什么?

2 个答案:

答案 0 :(得分:2)

有几种很好的模式可以处理异步重入,即如果用户操作在其已经在飞行中时调用异步方法会发生什么。我在这里写了几篇文章:

http://blogs.msdn.com/b/lucian/archive/2014/03/03/async-re-entrancy-and-the-patterns-to-deal-with-it.aspx

我认为您的问题是模式5(下面的代码)的一个特例。

但是,请注意您的规格中的一个奇怪之处。用户可能会足够快地点击以获得序列“添加”,然后是“添加”(例如,如果介入的“删除”在第二次单击“添加到达”之前甚至没有机会开始执行)。因此,请以您自己的方案特定方式防止这种情况发生。

 async  Task  Button1Click()
 {
     // Assume we're being called on UI thread... if not, the two assignments must be made atomic.
     // Note: we factor out "FooHelperAsync" to avoid an await between the two assignments. 
     // without an intervening await. 
      if  (FooAsyncCancellation !=  null ) FooAsyncCancellation.Cancel();
      FooAsyncCancellation  =  new  CancellationTokenSource ();
      FooAsyncTask  = FooHelperAsync(FooAsyncCancellation.Token);

      await  FooAsyncTask;
 }

 Task  FooAsyncTask;
 CancellationTokenSource  FooAsyncCancellation;

 async  Task  FooHelperAsync( CancellationToken  cancel)
 {
      try  {  if  (FooAsyncTask !=  null )  await  FooAsyncTask; }
      catch  ( OperationCanceledException ) { }
     cancel.ThrowIfCancellationRequested();
      await  FooAsync(cancel);
 }

 async  Task  FooAsync( CancellationToken  cancel)
 {
     ...
 }

答案 1 :(得分:0)

我建议在触发请求时禁用ToggleButton按钮并显示不确定的ProgressBar并隐藏ProgressBar并在完成时启用ToggleButton