如何取消Prism EventAggregator中排队的运行中任务
解决方案:用CancellationTokenSource取消旧任务,优先处理新请求
你的问题核心在于EventAggregator的默认订阅处理是串行的,加上异步任务没有取消机制,导致新请求必须等旧任务完成才能执行。我们可以通过CancellationTokenSource来跟踪并取消正在运行的旧任务,让新请求立即接管。下面是具体的实现步骤:
1. 在ViewModel中添加取消令牌管理字段
首先在你的ViewModel里维护一个CancellationTokenSource实例,用来跟踪当前正在执行的异步任务:
private CancellationTokenSource _currentTaskCts;
2. 修改事件订阅逻辑,收到新事件时先取消旧任务
每次收到新的SelectionUpdatedEvent时,先取消之前的任务,再创建新的令牌源,确保新任务能立即启动:
_eventAggregator.GetEvent<SelectionUpdatedEvent>().Subscribe(async selectedEntries => { // 取消并清理之前的任务令牌 _currentTaskCts?.Cancel(); _currentTaskCts?.Dispose(); // 创建新的令牌源,用于当前任务 _currentTaskCts = new CancellationTokenSource(); try { // 传递令牌给处理方法 await UpdateSelectedEntries(selectedEntries, _currentTaskCts.Token); } catch (OperationCanceledException) { // 任务被取消,这里可以记录日志或者做一些状态重置,无需抛出异常 Console.WriteLine("之前的计算任务已被取消"); } }, ThreadOption.PublisherThread); // 保持和发布线程一致(这里是UI线程)
3. 修改异步处理方法,支持取消令牌
更新UpdateSelectedEntries和RecalculateAll方法,让它们接受CancellationToken,并传递给需要取消的异步操作:
private async Task UpdateSelectedEntries(EntryViewModel[] selectedEntries, CancellationToken token) { _selectedEntries = selectedEntries; RaisePropertyChanged(nameof(SelectedText)); RaisePropertyChanged(nameof(SelectedBreakdown)); await RecalculateAll(token); } private async Task RecalculateAll(CancellationToken token) { try { var entryIds = _selectedEntries.SelectMany(s => s.StatusInfo.ValidEntryIds) .OrderBy(t => t) .ToArray(); ProcessingCount++; // 将令牌传递给GetResults,让远程请求能响应取消 var results = await _resultsService.GetResults(_resultType, entryIds, token); // 后续更新结果网格的逻辑... } catch (OperationCanceledException) { // 任务被取消,重置处理状态 if (ProcessingCount > 0) ProcessingCount--; throw; // 抛出给上层捕获处理 } finally { // 确保处理计数被正确重置,无论成功还是取消 if (ProcessingCount > 0) ProcessingCount--; } }
4. 让ResultsService支持取消令牌
修改GetResults方法,让它接受CancellationToken,并在内部使用它来响应取消(比如HTTP请求、数据库查询等操作都可以支持取消):
public async Task<YourResultModel[]> GetResults(ResultType resultType, int[] entryIds, CancellationToken token) { // 示例:如果是HttpClient请求,直接传递令牌 using var client = new HttpClient(); var url = $"https://your-api.com/results?type={resultType}&ids={string.Join(",", entryIds)}"; var response = await client.GetAsync(url, token); // 确保请求成功后再解析结果 response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync<YourResultModel[]>(token); }
特殊情况处理:如果GetResults不支持取消
如果你的GetResults方法本身不支持取消(比如调用了第三方不支持取消的API),可以用Task.WhenAny来包装,强制终止等待:
private async Task RecalculateAll(CancellationToken token) { try { // ...前面的代码... var getResultsTask = _resultsService.GetResults(_resultType, entryIds); // 创建一个可取消的等待任务 var cancellationWaitTask = Task.Delay(Timeout.Infinite, token); // 等待第一个完成的任务 var completedTask = await Task.WhenAny(getResultsTask, cancellationWaitTask); if (completedTask == cancellationWaitTask) { // 任务被取消,抛出取消异常 throw new OperationCanceledException(token); } // 获取正常结果 var results = await getResultsTask; // ...后续逻辑... } // ...catch和finally逻辑... }
5. 清理资源(可选但推荐)
在ViewModel销毁时,记得取消当前任务并释放令牌源,避免内存泄漏:
public void Dispose() { _currentTaskCts?.Cancel(); _currentTaskCts?.Dispose(); }
为什么这样有效?
- 每次新选择事件到来时,旧的异步任务会被立即取消,不再占用EventAggregator的处理管道。
CancellationTokenSource能精准控制任务的生命周期,确保资源被正确清理。- 取消操作会让旧的远程请求(如果支持取消)立即终止,节省服务器资源和网络带宽。
内容的提问来源于stack exchange,提问作者NZJames
相关产品推荐
相关产品推荐

