异步 CancellationTokenSource 取消的最佳实践

4

我在用户界面上有一个组合框,在选择更改时,它会异步地去访问网络服务,以获取一些要在用户界面上显示的信息(使用新的 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的最佳方法是什么,因为我每次都创建一个新的?

提前感谢!


问题解决了吗?我现在有完全相同的问题。 - NS.X.
没有,问题还没有解决,但现在回头看我的问题,我认为正确的做法是将CancellationTokenSource的Token作为可选参数(即public async Task<Connections> ConnectionsAsync(CancellationToken token = null))传递。这样,调用ConnectionsAsync()函数的用户就负责设置一个新的CancellationTokenSource并传入其Token。 - deadlydog
所以在我的示例中,这将发生在我的组合框的SelectionChanged事件中;我会在现有的CancellationTokenSource上调用Cancel(),创建一个新的,并将其Token传递到ConnectionsAsync()函数中。但最终对于我的特定应用程序,我最终没有使用取消操作。 - deadlydog
谢谢更新。我会继续查找,因为这是我UI应用程序中常见的模式。 - NS.X.
1个回答

2

看起来Reactive扩展是天造地设的一对。在从组合框观察事件之前定义一个节流时间(假设为300毫秒),然后创建任务。

代码片段订阅文本框更改事件,但您可以了解思路:

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秒钟。 - deadlydog
你考虑过实现(预加载的)本地缓存机制吗?你真的需要从组合框更改选择时进行实时数据检索吗? - Magnus Johansson
可能可以通过缓存数据来处理我的特定情况,因为只有大约75个人可以更改我正在检索的数据。然而,原始问题仍然有效; 假设我的组合框列出了我的Twitter帐户,并且当我选择一个帐户时,我想要查看该帐户的实时动态。这是一个完美的例子,您总是需要实时数据。 - deadlydog
1
原问题可能是有效的,但你提到的具体实现是糟糕的设计。当用户仅通过组合框切换帐户视图时,为什么要强制进行实时更新?为每个Twitter帐户提供本地缓存,并使用最后更新日期时间字段,在每个帐户的缓存过期时才进行服务调用。或者(更好的选择),对每个流进行定期后台更新。尽量保持简单,你原来的任务实现似乎对你的特定情况过于复杂。 - Magnus Johansson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接