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



我有一个Windows Phone客户端,它有一个蒙皮切换按钮,充当"收藏夹"按钮。然后,选中的属性被双向绑定到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应该保持对用户输入的响应。

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

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

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

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

但是,请注意您的规范中有一个奇怪之处。用户点击的速度可能足够快,以至于你得到的顺序是Add,然后是Add(例如,如果中间的Remove在第二次点击Add之前甚至没有机会开始执行)。因此,请以您自己的特定场景的方式对此进行保护。

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)
{
...
}

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

最新更新