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

在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 13:50:47