在ASP.NET Core中如何中断指定FLV文件下载且不损坏流
实现单个FLV下载的精准中断(不损坏文件、不影响并行任务)
要实现目标,核心是给每个下载请求分配独立的取消令牌,通过唯一标识追踪并精准中断指定任务,同时确保流的正确关闭以避免FLV文件损坏。以下是具体实现方案:
关键思路
- 用线程安全字典存储每个下载请求的取消令牌源(
CancellationTokenSource),键为请求唯一标识(downloadId) - 在下载过程中传入取消令牌,触发取消时会终止当前流拷贝操作,且已写入的FLV数据是完整的块(FLV基于帧结构,客户端可识别文件结束,不会损坏已下载部分)
- 中断时通过
downloadId找到对应令牌源并取消,不影响其他并行下载任务
修改后的完整代码
var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); // 线程安全字典:存储下载ID与对应的取消令牌源 var downloadCtsStore = new ConcurrentDictionary<string, CancellationTokenSource>(); // 复用HttpClient(推荐用IHttpClientFactory,避免频繁创建销毁导致socket耗尽) builder.Services.AddHttpClient(); app.MapGet("/stream", async (context, [FromQuery] string downloadId, IHttpClientFactory httpClientFactory) => { if (string.IsNullOrEmpty(downloadId)) { context.Response.StatusCode = StatusCodes.Status400BadRequest; await context.Response.WriteAsync("缺少必填参数downloadId"); return; } var cts = new CancellationTokenSource(); // 尝试添加到字典,避免重复ID的下载请求 if (!downloadCtsStore.TryAdd(downloadId, cts)) { context.Response.StatusCode = StatusCodes.Status409Conflict; await context.Response.WriteAsync("该downloadId对应的下载任务已存在"); return; } try { var httpClient = httpClientFactory.CreateClient(); var request = new HttpRequestMessage(HttpMethod.Get, "你的FLV文件源URL"); // 发送请求时传入取消令牌,支持中途中断源请求 var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cts.Token); // 设置正确的FLV MIME类型 context.Response.ContentType = "video/x-flv"; context.Response.Headers.Add("Content-Disposition", "attachment; filename=\"file.flv\""); context.Response.Headers.Add("Transfer-Encoding", "chunked"); using var responseStream = await response.Content.ReadAsStreamAsync(cts.Token); // 带取消令牌的流拷贝,中断时会触发OperationCanceledException await responseStream.CopyToAsync(context.Response.Body, 81920, cts.Token); } catch (OperationCanceledException) { // 捕获取消异常,返回正常响应 context.Response.StatusCode = StatusCodes.Status200OK; await context.Response.WriteAsync("下载已成功中断"); } catch (Exception ex) { context.Response.StatusCode = StatusCodes.Status500InternalServerError; await context.Response.WriteAsync($"下载失败:{ex.Message}"); } finally { // 无论成功/失败/取消,都清理令牌源和字典条目,避免内存泄漏 downloadCtsStore.TryRemove(downloadId, out _); cts.Dispose(); } }); app.MapGet("/stop", async (context, [FromQuery] string downloadId) => { if (string.IsNullOrEmpty(downloadId)) { context.Response.StatusCode = StatusCodes.Status400BadRequest; await context.Response.WriteAsync("缺少必填参数downloadId"); return; } // 从字典中移除并获取令牌源,触发取消 if (downloadCtsStore.TryRemove(downloadId, out var cts)) { cts.Cancel(); cts.Dispose(); await context.Response.WriteAsync("已成功停止指定下载任务"); } else { context.Response.StatusCode = StatusCodes.Status404NotFound; await context.Response.WriteAsync("未找到对应下载任务"); } }); app.Run();
注意事项
- 客户端使用方式:客户端发起下载时需生成唯一
downloadId(如Guid),通过查询参数传递给/stream;中断时将同一downloadId传递给/stop即可精准终止该任务 - FLV文件完整性:中断时流拷贝会立即停止,已写入客户端的FLV数据是完整的帧块,客户端可正常识别并播放已下载部分,不会出现文件损坏
- 资源优化:使用
IHttpClientFactory复用HttpClient,避免频繁创建实例导致的socket资源耗尽问题 - 异常处理:捕获
OperationCanceledException以区分正常取消和其他异常,确保响应友好
内容的提问来源于stack exchange,提问作者EmbeEmbe
相关产品推荐
相关产品推荐

