【问题标题】:How to use a single async web service call repeatedly in a stream of cancellable requests?如何在可取消的请求流中重复使用单个异步 Web 服务调用?
【发布时间】:2017-04-10 15:43:04
【问题描述】:

在我的 WPF UI 中,我有一个客户列表。我还有一个用于获取单个客户资料的 Web API 服务。服务器端和客户端都是使用async/await 构建的,并且可以取消。

当用户从 ComboBox 中选择“客户 A”时,它会触发服务器调用以获取客户资料。 2 秒后(服务器操作方法的人为持续时间),数据返回并显示。如果在这 2 秒内选择了“客户 B”,我的代码会取消第一个请求并触发第二个请求。

这一切都很好,忙碌/取消逻辑相当幼稚,如果用户很快选择不同的客户,就无法正确取消。例如,如果我快速按下向下箭头键(组合框具有焦点)10 次,则在服务器端正确取消了 5 个请求,而没有正确取消 5 个请求。虽然这不是世界末日,但我不想无缘无故地使用并行运行的潜在大型数据库查询来消耗服务器资源。

这是我的客户端代码:

CancellationTokenSource _customerProfileCts;

//called when a new customer item is selected from the UI's ComboBox
private async void TriggerGetCustomerProfile()
{
    //if there is already a customer profile fetch operation in progress, we just want to cancel it and start a new one. 
    if (IsBusyFetchingCustomerProfile)
    {
        _customerProfileCts.Cancel();
    }

    try
    {
        IsBusyFetchingCustomerProfile = true;
        IsCustomerProfileReady = false;
        await GetCustomerProfile();
    }
    finally
    {
        IsBusyFetchingCustomerProfile = false;
        _customerProfileCts = null;
    }
}

private async Task GetCustomerProfile()
{
    _customerProfileCts = new CancellationTokenSource();
    await _customerSvc.GetCustomerProfileReport(SelectedCustomer.Id, _customerProfileCts.Token);
    //logic for checking result of web call and distributing received data omitted
}

我觉得这种事情应该有一些成熟的模式,确保如果选择了新的 UI 项目,无论用户选择多快,都会取消每个请求。

事实上,这不是反应式扩展的突出用例之一吗?我看到很多关于假设问题的提及,例如在文本框中输入击键后调用搜索 Web 服务,但我没有找到任何处理取消先前发送的请求的示例。

我只需要干净且坚如磐石的东西,希望可以打包并隐藏复杂性的东西,以便这种可取消异步获取可以在我的应用程序的其他地方无故障地使用。

【问题讨论】:

    标签: .net async-await system.reactive


    【解决方案1】:

    您的问题是您的finally 正在修改_customerProfileCts,这可以null 输出仍在被其他调用使用的实例。如果你把它移到Cancel 之后,那么它应该可以正常工作。其实你可以把它和GetCustomerProfile中的修改结合起来,这样:

    CancellationTokenSource _customerProfileCts;
    
    private async void TriggerGetCustomerProfile()
    {
      if (_customerProfileCts != null)
      {
        _customerProfileCts.Cancel();
      }
      _customerProfileCts = new CancellationTokenSource();
      var token = _customerProfileCts.Token;
    
      try
      {
        IsBusyFetchingCustomerProfile = true;
        IsCustomerProfileReady = false;
        await GetCustomerProfile(token);
      }
      finally
      {
        IsBusyFetchingCustomerProfile = false;
      }
    }
    
    private async Task GetCustomerProfile(CancellationToken token)
    {
      await _customerSvc.GetCustomerProfileReport(SelectedCustomer.Id, token);
    }
    

    【讨论】:

    • 效果很好!有一个小的副作用,我的IsBusyFetchingCustomerProfile 忙碌指示器在取消呼叫的finally 中被重置,在新呼叫后不久将其设置为高。我用你的AsyncLock 包装了try/finally,它解决了这个问题。
    猜你喜欢
    • 2011-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-05
    • 1970-01-01
    • 2011-04-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多