You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何取消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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 04:08:36