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

ASP.NET Core中如何通过DELETE端点取消长时间运行的任务?

长时任务API的取消方案实现

现有API与问题背景

你已经实现了长时任务的启动、进度查询接口,核心问题是如何实现可靠的任务取消功能——既不想让服务层持有令牌源,也不能用静态令牌源限制并发任务,而Action自带的CancellationToken仅适用于当前请求生命周期,无法满足跨请求取消的需求。

你的现有代码如下:

[HttpGet]
public IActionResult LongRunning()
{
    var taskId = longRunningService.CreateTask();

    var progress = new ProgressDto
    {
        Id = taskId,
        Status = "created"
    };

    return CreatedAtAction("LongRunningProgress", new { id = taskId }, progress);
}

[HttpGet("{id:int}")]
public async Task<ActionResult<ProgressDto>> LongRunningProgress(int id)
{
    var task = longRunningService.GetTask(id);
    // 根据任务完成状态或部分结果返回
}

[HttpDelete("{id:int}")]
public ActionResult CancelLongRunning(int id)
{
    // 如何实现?
}

可行解决方案

方案1:服务层维护任务与CancellationTokenSource的映射

这是单实例场景下最直接的方案,核心是在服务层用线程安全的字典,将每个任务ID与对应的CancellationTokenSource绑定,同时做好资源清理。

服务层核心实现

public class LongRunningService
{
    // 线程安全字典:任务ID -> 取消令牌源
    private readonly ConcurrentDictionary<int, CancellationTokenSource> _taskCtsMap = new();
    // 存储任务状态、进度等信息
    private readonly ConcurrentDictionary<int, TaskInfo> _taskInfos = new();
    private int _nextTaskId = 0;

    public int CreateTask()
    {
        var taskId = Interlocked.Increment(ref _nextTaskId);
        var cts = new CancellationTokenSource();
        
        _taskCtsMap.TryAdd(taskId, cts);
        _taskInfos.TryAdd(taskId, new TaskInfo { Status = "created" });

        // 启动后台长时任务,传入取消令牌
        _ = RunLongTask(taskId, cts.Token);
        
        return taskId;
    }

    public TaskInfo GetTask(int id)
    {
        _taskInfos.TryGetValue(id, out var info);
        return info;
    }

    public bool CancelTask(int id)
    {
        // 移除令牌源并取消任务
        if (_taskCtsMap.TryRemove(id, out var cts))
        {
            cts.Cancel();
            cts.Dispose(); // 及时释放资源
            // 更新任务状态
            if (_taskInfos.TryGetValue(id, out var info))
            {
                info.Status = "cancelled";
            }
            return true;
        }
        return false; // 任务不存在或已完成
    }

    private async Task RunLongTask(int taskId, CancellationToken cancellationToken)
    {
        var taskInfo = _taskInfos[taskId];
        taskInfo.Status = "running";

        try
        {
            // 模拟长时任务逻辑,定期检查取消信号
            for (int i = 0; i < 10; i++)
            {
                // 检查取消请求
                if (cancellationToken.IsCancellationRequested)
                {
                    taskInfo.Status = "cancelled";
                    return;
                }
                // 执行子任务,传入取消令牌
                await Task.Delay(1000, cancellationToken);
                taskInfo.Progress = (i + 1) * 10;
            }

            taskInfo.Status = "completed";
            taskInfo.Result = "任务最终结果";
        }
        catch (OperationCanceledException)
        {
            taskInfo.Status = "cancelled";
        }
        finally
        {
            // 清理剩余的令牌源
            _taskCtsMap.TryRemove(taskId, out var cts);
            cts?.Dispose();
        }
    }
}

// 任务信息辅助类
public class TaskInfo
{
    public string Status { get; set; }
    public int Progress { get; set; }
    public string Result { get; set; }
}

控制器取消接口实现

[HttpDelete("{id:int}")]
public ActionResult CancelLongRunning(int id)
{
    var isCancelled = longRunningService.CancelTask(id);
    return isCancelled ? NoContent() : NotFound();
}

关键注意点

  • 用ConcurrentDictionary保证多请求下的线程安全
  • 任务完成或取消后必须清理CancellationTokenSource,避免内存泄漏
  • 长时任务内部要主动检查取消令牌,或在调用支持取消的异步方法时传入令牌

方案2:分布式取消(集群部署场景)

如果API是多实例集群部署,单实例内存字典无法跨实例生效,此时可以用分布式缓存(如Redis)存储取消信号:

  • 创建任务时,在Redis中写入task:{id}:cancel键,值为false
  • 取消任务时,将该键值更新为true
  • 长时任务定期轮询Redis中的该键,若值为true则终止任务
  • 任务完成后删除该键

这种方案需要引入分布式缓存依赖,但能适配集群环境。

误区澄清

  • 禁止使用控制器静态CancellationTokenSource:会导致所有任务共享同一令牌,取消一个任务会触发所有任务取消,完全不符合并发需求
  • Action自带的CancellationToken仅对应当前请求的生命周期,仅在客户端断开连接时触发,无法实现主动的跨请求取消,不适用当前场景

内容的提问来源于stack exchange,提问作者FlamingDrake

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 10:52:27