如何触发其他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
相关产品推荐
相关产品推荐

