【问题标题】:Best practice for consistently cancelling Async CancellationTokenSource一致取消 Async CancellationTokenSource 的最佳实践
【发布时间】:2012-07-07 00:10:09
【问题描述】:

所以我的 UI 上有一个组合框,它在 SelectionChanged 上异步转到 Web 服务,以拉回一些要在 UI 上显示的信息(使用新的 C#5 async/await 关键字)。我要做的是在发送新请求之前取消当前的异步请求;例如,如果用户使用键盘快速循环浏览所有组合框项,则 SelectionChanged 事件可能会在第一个异步请求返回之前触发多次(生成多个异步请求)。

所以我从组合框的 SelectionChanged 事件中调用的异步函数如下所示:

public async Task<Connections> ConnectionsAsync()
{
    return await Task.Factory.StartNew(() => Connections, _cancellationTokenSource.Token);
}

Connections 是一个属性,它会触发并访问 Web 服务。所以因为 CancellationTokenSource 一旦被取消就不能重用,我正在考虑这样做:

public async Task<Connections> ConnectionsAsync()
{
    _cancellationTokenSource.Cancel();
    _cancellationTokenSource = new CancellationTokenSource();
    return await Task.Factory.StartNew(() => Connections, _cancellationTokenSource.Token);
}

但问题是,有时我会在没有异步命令运行时调用 Cancel()(例如,第一次调用此函数时);因此,如果我连接任何取消事件处理程序,即使在我发出异步请求之前,它们也会被调用。

有没有办法检查异步请求是否已经在运行?除了我做类似的事情:

public async Task<Connections> ConnectionsAsync()
{
    if (_runningAsyncCommand)
        _cancellationTokenSource.Cancel();
    _cancellationTokenSource = new CancellationTokenSource();
    _runningAsyncCommand = true;
    return await Task.Factory.StartNew(() => Connections, _cancellationTokenSource.Token);
    _runningAsyncCommand = false;
}

我有几个异步函数都使用相同的 CancellationTokenSource,所以我必须在所有这些函数中实现这个“管道”。这是最好的方法吗?或者,还有更好的方法?

另外,如果我公开 _cancellationTokenSource 以便其他类可以使用它注册取消委托,那么将这些委托“转移”到新的 CancellationTokenSource 的最佳方法是什么,因为我每次都在创建一个新委托?

提前致谢!

【问题讨论】:

  • 解决了吗?我现在有完全相同的问题。
  • 不,它没有解决,但现在回顾我的问题,我认为正确的做法是将 CancellationTokenSource 的令牌作为可选参数(即 public async Task ConnectionsAsync(CancellationToken token =空))。这样,调用 ConnectionsAsync() 函数的用户负责设置一个新的 CancellationTokenSource 并传入它的 Token。
  • 所以在我的示例中,这将发生在我的组合框的 SelectionChanged 事件中;我会在现有的 CancellationTokenSource 上调用 Cancel(),创建一个新的,并将它的 Token 传递给 ConnectionsAsync() 函数。最后,尽管对于我的特定应用程序,我最终没有使用取消。
  • 感谢您的更新。我会继续寻找,因为这是我的 UI 应用程序中的常见模式。

标签: c# asynchronous async-await cancellationtokensource


【解决方案1】:

对于响应式扩展来说,这简直是天作之合。定义在创建任务之前观察 Combobox 中的事件时必须经过的节流时间(比如 300 毫秒)

代码 sn-p 订阅 TextBox 更改事件,但你会明白的:

var input (from evt in Observable.FromEvent<EventArgs>(txt, "TextChanged")
select ((TextBox)evt.Sender).Text)
.Throttle(TimeSpan.FromSeconds(1))
.DistinctUntilChanged();

using (input.Subscribe(inp => Console.WriteLine("You wrote: " + inp)))
{
  // Do stuff
}

【讨论】:

  • 一个不错的解决方案,但仍然不完美。假设我对 Web 服务的请求需要 3 秒才能返回。用户可能仍然每 2 秒更改一次组合框选择,在这种情况下我仍然有同样的问题。我不想将油门设置得太高,例如 2 秒,否则每次他们更改选择时,在发送请求之前都会有 2 秒的延迟;所以从更改选择到更新 UI 总共需要 5 秒。
  • 您是否考虑过实现(预加载)本地缓存机制?您真的需要从组合框更改选择中进行实时数据检索吗?
  • 可能能够为我的特定情况缓存数据,因为只有大约 75 人可以更改我正在检索的数据。原来的问题仍然有效;假设我的组合框列出了我的 Twitter 帐户,当我选择一个时,我想查看该帐户的实时提要。这是您始终需要实时数据的完美示例。
  • 最初的问题可能是有效的,但你提到的具体实现是糟糕的设计。当用户只是通过组合框切换帐户视图时,您到底为什么要通过网络强制实时更新?使用最后更新的日期时间字段为每个 Twitter 帐户提要实施本地缓存,并且仅在每个帐户的缓存过期时才进行服务调用。或者(甚至更好),对每个流进行定期后台更新。尽量保持简单,对于您的特定场景,您的原始任务实现似乎过于复杂。
猜你喜欢
  • 1970-01-01
  • 2018-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-19
  • 2011-03-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多