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

如何触发其他CancellationToken?多控制器文件下载中断方案咨询

嘿,我来帮你搞定这个跨控制器取消下载的问题!你之前的思路方向是对的——用IMemoryCache存CancellationTokenSource,但问题大概率出在下载操作没有正确监听取消令牌的触发,或者令牌的关联/清理环节有漏洞。下面给你一套完整的可落地方案:

核心思路

要实现删除时中断下载,关键要做到两点:

  • 每个下载请求对应唯一的CancellationTokenSource,并能被删除接口精准定位
  • 下载流的异步操作全程监听取消令牌,确保触发取消时能立刻终止流读取

步骤1:设计唯一请求标识

给每个下载请求分配一个唯一的downloadId(比如前端请求下载前先获取后端生成的GUID,或者前端自己生成),这个ID会作为IMemoryCache的键,让删除接口能精准找到对应的取消令牌源。

步骤2:下载控制器的实现

在下载接口中创建CancellationTokenSource并存入缓存,同时确保流读取操作全程绑定取消令牌,并且在取消触发时能捕获异常并清理资源:

[ApiController]
[Route("api/download")]
public class DownloadController : ControllerBase
{
    private readonly IMemoryCache _cache;
    private readonly IWebHostEnvironment _env;

    public DownloadController(IMemoryCache cache, IWebHostEnvironment env)
    {
        _cache = cache;
        _env = env;
    }

    [HttpGet("{fileName}")]
    public async Task<IActionResult> Download(string fileName, [FromQuery] string downloadId)
    {
        if (string.IsNullOrEmpty(downloadId))
            return BadRequest("请提供有效的downloadId");

        var filePath = Path.Combine(_env.ContentRootPath, "Files", fileName);
        if (!System.IO.File.Exists(filePath))
            return NotFound();

        // 创建取消令牌源并存入缓存,设置30分钟过期防止内存泄漏
        var cts = new CancellationTokenSource();
        _cache.Set(downloadId, cts, TimeSpan.FromMinutes(30));

        try
        {
            // 打开文件流时设置FileShare.ReadWrite,允许删除操作同时访问文件
            using var fileStream = new FileStream(
                filePath, 
                FileMode.Open, 
                FileAccess.Read, 
                FileShare.ReadWrite, 
                bufferSize: 4096, 
                useAsync: true);

            // 返回带取消监听的文件流
            return File(await StreamWithCancellation(fileStream, cts.Token), "application/octet-stream", fileName);
        }
        catch (OperationCanceledException)
        {
            // 捕获取消异常,返回明确的响应
            return StatusCode(StatusCodes.Status499ClientClosedRequest, "下载已被取消");
        }
        finally
        {
            // 无论成功还是取消,都清理缓存和令牌源
            _cache.Remove(downloadId);
            cts.Dispose();
        }
    }

    // 封装带取消检查的流读取逻辑
    private async Task<MemoryStream> StreamWithCancellation(Stream source, CancellationToken token)
    {
        var memoryStream = new MemoryStream();
        var buffer = new byte[4096];
        int bytesRead;

        while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, token)) > 0)
        {
            // 额外检查取消信号,确保能快速响应
            token.ThrowIfCancellationRequested();
            await memoryStream.WriteAsync(buffer, 0, bytesRead, token);
        }

        memoryStream.Position = 0;
        return memoryStream;
    }
}

步骤3:删除控制器的实现

在删除接口中,通过downloadId从缓存获取令牌源并触发取消,等待一小段时间让下载操作完成终止,再尝试删除文件:

[ApiController]
[Route("api/file")]
public class FileController : ControllerBase
{
    private readonly IMemoryCache _cache;
    private readonly IWebHostEnvironment _env;

    public FileController(IMemoryCache cache, IWebHostEnvironment env)
    {
        _cache = cache;
        _env = env;
    }

    [HttpDelete("{fileName}")]
    public async Task<IActionResult> DeleteFile(string fileName, [FromQuery] string downloadId)
    {
        var filePath = Path.Combine(_env.ContentRootPath, "Files", fileName);
        if (!System.IO.File.Exists(filePath))
            return NotFound();

        // 触发对应下载请求的取消操作
        if (!string.IsNullOrEmpty(downloadId) && _cache.TryGetValue(downloadId, out CancellationTokenSource cts))
        {
            try
            {
                cts.Cancel();
                // 等待500毫秒让下载操作处理取消逻辑
                await Task.Delay(500);
            }
            catch (ObjectDisposedException)
            {
                // 令牌源已被释放,说明下载已经完成
            }
        }

        // 尝试删除文件
        try
        {
            System.IO.File.Delete(filePath);
            return Ok("文件已成功删除");
        }
        catch (IOException ex)
        {
            return StatusCode(StatusCodes.Status500InternalServerError, $"删除失败:{ex.Message}");
        }
    }
}

关键注意事项

  • 令牌生命周期管理:一定要在下载完成/取消后清理缓存和释放CancellationTokenSource,避免内存泄漏。
  • 文件共享权限:下载流打开时设置FileShare.ReadWrite,否则即使取消下载,文件可能仍被锁定无法删除。
  • 取消信号监听:确保所有异步流操作都传入取消令牌,手动调用token.ThrowIfCancellationRequested()能让取消响应更及时。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 03:23:37