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

.NET Core WebAPI中FileStream文件被占用IOException问题求助

.NET Core WebAPI 文件占用IOException问题修复

问题描述

在.NET Core WebAPI中编写视频片段查看控制器,发送请求时抛出错误:

System.IO.IOException: 'The process cannot access the file '...Clips\8ViAhBctO.mp4' because it is being used by another process.'

已尝试在返回文件前Dispose文件流,但问题仍未解决,环境为Windows。

原控制器代码如下:

public IActionResult ViewClip(string id)
{
    filestream = new FileStream($"Clips/{id}.mp4", FileMode.Open);
    var response = Response;
    var length = filestream.Length;
    var range = response.HttpContext.GetRanges(length);

    if (IsRangeRequest(range))
    {
        response.StatusCode = 206;

        if (!IsMultipartRequest(range))
        {
            response.Headers.Add("Content-Range", $"bytes {range.Ranges.First().From}-{range.Ranges.First().To}/{length}");
        }

        foreach (var rangeValue in range.Ranges)
        {
            if (IsMultipartRequest(range)) 
            {
                response.WriteAsync("--<qwe123>--");
                response.WriteAsync(Environment.NewLine);
                response.WriteAsync("Content-type: video/mp4");
                response.WriteAsync(Environment.NewLine);
                response.WriteAsync($"Content-Range: bytes {range.Ranges.First().From}-{range.Ranges.First().To}/{length}");
                response.WriteAsync(Environment.NewLine);
            }

            WriteDataToResponseBody(rangeValue, response);

            if (IsMultipartRequest(range))
            {
                response.WriteAsync(Environment.NewLine);
            }
        }

        if (IsMultipartRequest(range))
        {
            response.WriteAsync("--<qwe123>--");
            response.WriteAsync(Environment.NewLine);
        }
        filestream.Dispose();
        return File(System.IO.File.ReadAllBytes($"Clips/{id}.mp4"), "application/octet-stream", $"{id}.mp4", enableRangeProcessing: true);
    }
    else
    {
        filestream.Dispose();
        return File(System.IO.File.ReadAllBytes($"Clips/{id}.mp4"), "application/octet-stream", $"{id}.mp4", enableRangeProcessing: true);
    }
}
private bool IsMultipartRequest(RangeHeaderValue range)
{
    return range != null && range.Ranges != null && range.Ranges.Count > 1;
}

private bool IsRangeRequest(RangeHeaderValue range)
{
    return range != null && range.Ranges != null && range.Ranges.Count > 0;
}
public static void WriteDataToResponseBody(RangeItemHeaderValue rangeValue, HttpResponse response)
{
    var startIndex = rangeValue.From ?? 0;
    var endIndex = rangeValue.To ?? 0;

    byte[] buffer = new byte[0x1000];
    long totalToSend = endIndex - startIndex;
    int count = 0;

    long bytesRemaining = totalToSend + 1;
    response.ContentLength = bytesRemaining;

    filestream.Seek(startIndex, SeekOrigin.Begin);

    while (bytesRemaining > 0)
    {
        try
        {
            if (bytesRemaining <= buffer.Length)
                count = filestream.Read(buffer, 0, (int)bytesRemaining);
            else
                count = filestream.Read(buffer, 0, buffer.Length);

            if (count == 0)
                return;

            response.Body.WriteAsync(buffer, 0, count);

            bytesRemaining -= count;
        }
        catch (IndexOutOfRangeException)
        {
            response.Body.FlushAsync();
            return;
        }
        finally
        {
            response.Body.FlushAsync();
        }
    }
}

核心错误原因

  • 重复打开文件:先通过filestream = new FileStream(...)打开文件,随后又调用File.ReadAllBytes(...)再次打开同一文件,此时第一个流未完全释放,导致文件被占用。
  • 类级别FileStream字段:filestream是控制器类的成员变量,多请求并发时会被多个请求共享,引发流操作冲突。
  • 异步操作未等待:response.WriteAsync等异步方法未加await,导致流操作未完成就执行filestream.Dispose()和返回文件操作,资源释放不彻底。
  • 重复Range处理:手动实现Range请求逻辑的同时,又启用了enableRangeProcessing: true,框架会再次处理Range请求,双重操作导致文件被多次占用。

修复方案与优化代码

最简修复:依赖框架内置Range处理

.NET Core的PhysicalFile方法已内置完善的Range请求支持,无需手动实现,直接替换原代码即可:

using System.IO;
using Microsoft.AspNetCore.Mvc;

public IActionResult ViewClip(string id)
{
    var filePath = Path.Combine("Clips", $"{id}.mp4");
    
    // 先检查文件是否存在,避免无效请求
    if (!System.IO.File.Exists(filePath))
    {
        return NotFound();
    }
    
    // 使用video/mp4作为Content-Type,支持浏览器直接播放
    return PhysicalFile(filePath, "video/mp4", enableRangeProcessing: true);
}

若需手动处理Range请求(特殊场景)

如果必须自定义Range处理逻辑,需注意以下几点:

  1. 使用局部FileStream,并用using语句自动释放资源,避免类级别字段的并发问题;
  2. 所有异步操作添加await,确保操作完成后再释放资源;
  3. 不要启用enableRangeProcessing: true,避免与自定义逻辑冲突;
  4. 确保每个请求使用独立的文件流实例。

示例代码(简化版):

public async Task<IActionResult> ViewClip(string id)
{
    var filePath = Path.Combine("Clips", $"{id}.mp4");
    if (!System.IO.File.Exists(filePath))
    {
        return NotFound();
    }

    using var filestream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
    var response = Response;
    var length = filestream.Length;
    var range = response.HttpContext.GetRanges(length);

    if (IsRangeRequest(range))
    {
        response.StatusCode = StatusCodes.Status206PartialContent;
        // 自定义Range处理逻辑,需确保所有异步操作加await
        // ... 此处编写你的自定义逻辑
        return new EmptyResult();
    }
    else
    {
        return File(filestream, "video/mp4");
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 19:02:05