.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处理逻辑,需注意以下几点:
- 使用局部FileStream,并用
using语句自动释放资源,避免类级别字段的并发问题; - 所有异步操作添加
await,确保操作完成后再释放资源; - 不要启用
enableRangeProcessing: true,避免与自定义逻辑冲突; - 确保每个请求使用独立的文件流实例。
示例代码(简化版):
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
相关产品推荐
相关产品推荐

