.NET Core图片上传Linux服务器报错:SafeHandle已关闭
问题描述
在.NET Core中实现图片上传功能,本地运行完全正常,但部署到印度的Ubuntu服务器后,执行流复制保存数据时抛出ObjectDisposedException,提示“Safe handle has been closed”。
错误日志
Unhandled exception. System.ObjectDisposedException: Safe handle has been closed. Object name: 'SafeHandle'. at System.Runtime.InteropServices.SafeHandle.DangerousAddRef(Boolean& success) at Interop.Sys.PRead(SafeHandle fd, Byte* buffer, Int32 bufferSize, Int64 fileOffset) at System.IO.RandomAccess.ReadAtOffset(SafeFileHandle handle, Span`1 buffer, Int64 fileOffset) at Microsoft.Win32.SafeHandles.SafeFileHandle.ThreadPoolValueTaskSource.ExecuteInternal() --- End of stack trace from previous location --- at Microsoft.Win32.SafeHandles.SafeFileHandle.ThreadPoolValueTaskSource.GetResult(Int16 token) at Microsoft.Win32.SafeHandles.SafeFileHandle.ThreadPoolValueTaskSource.System.Threading.Tasks.Sources.IValueTaskSource<System.Int32>.GetResult(Int16 token) at Microsoft.AspNetCore.WebUtilities.FileBufferingReadStream.ReadAsync(Memory`1 buffer, CancellationToken cancellationToken) at Microsoft.AspNetCore.Http.ReferenceReadStream.ReadAsync(Memory`1 buffer, CancellationToken cancellationToken) at System.IO.Stream.<CopyToAsync>g__Core|29_0(Stream source, Stream destination, Int32 bufferSize, CancellationToken cancellationToken) at Microsoft.AspNetCore.Http.FormFile.CopyToAsync(Stream target, CancellationToken cancellationToken) at ContractAPI.Helper.AppBaseService.CreateFile(String savePath, IFormFile formFile) in AppBaseService.cs:line 38 at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__128_1(Object state) at System.Threading.QueueUserWorkItemCallback.<>c.<.cctor>b__6_0(QueueUserWorkItemCallback quwi) at System.Threading.QueueUserWorkItemCallback.Execute() at System.Threading.ThreadPoolWorkQueue.Dispatch() at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart() at System.Threading.Thread.StartCallback()
相关代码
Program.cs
public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); }
Startup.cs
public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddDbContext<ContractMakerContext>(options => { options.UseMySQL(Configuration.GetConnectionString("Default")); }); services.AddScoped<ICompanyInfoService, CompanyInfoService>(); services.AddControllers(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseSwagger(); app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "ContractAPI v1")); } app.UseHttpsRedirection(); app.UseRouting(); app.UseStaticFiles(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } }
CompanyInfoService.cs
public interface ICompanyInfoService { Task<ResponseUserCompanyInfo> updateUserCompanyInfoWithFile(CompanyInfoWithFile info); } public class CompanyInfoService : ICompanyInfoService { public static IWebHostEnvironment _environment; public CompanyInfoService(IWebHostEnvironment environment) { _environment = environment; } public async Task<ResponseUserCompanyInfo> updateUserCompanyInfoWithFile(CompanyInfoWithFile info) { ResponseUserCompanyInfo response = new ResponseUserCompanyInfo(); CreateFile($"{_environment.WebRootPath}{Constants.SaveCompanyImagePath}", info.company_logo_url); return response; } private async void CreateFile(string savePath, IFormFile formFile) { if (!Directory.Exists(savePath)) { Directory.CreateDirectory(savePath); } if (formFile.Length <= 0) return; using (var filestream = File.Create($"{savePath}{formFile.FileName}")) { await formFile.CopyToAsync(filestream); // 异常发生处 } } }
CompanyInfoController.cs
[Route("api/[controller]")] [ApiController] public class CompanyInfoController : ControllerBase { ICompanyInfoService _service; public CompanyInfoController(ICompanyInfoService service) { _service = service; } [HttpPost("updateUserCompanyInfoWithFile")] public async Task<IActionResult> updateUserCompanyInfoWithFile([FromForm] CompanyInfoWithFile info) { ResponseUserCompanyInfo response = await _service.updateUserCompanyInfoWithFile(info); return MakeResponse(response, response.error_code); } }
问题原因及解决方案
核心问题:异步方法误用async void
CreateFile方法声明为async void是触发异常的关键:
async void无法被调用方等待,请求处理流程会直接继续执行,导致ASP.NET Core提前回收请求上下文(包括IFormFile对应的流资源)。- 服务器环境下请求资源回收更快,异步文件保存操作尚未完成就失去了对
IFormFile流的访问权限,从而抛出SafeHandle已关闭的异常。
修复步骤
- 修正异步方法的返回类型与调用方式
将CreateFile改为返回Task,并在调用时使用await等待操作完成:
// 修改updateUserCompanyInfoWithFile方法 public async Task<ResponseUserCompanyInfo> updateUserCompanyInfoWithFile(CompanyInfoWithFile info) { ResponseUserCompanyInfo response = new ResponseUserCompanyInfo(); await CreateFile($"{_environment.WebRootPath}{Constants.SaveCompanyImagePath}", info.company_logo_url); return response; } // 修改CreateFile方法的返回类型 private async Task CreateFile(string savePath, IFormFile formFile) { if (!Directory.Exists(savePath)) { Directory.CreateDirectory(savePath); } if (formFile.Length <= 0) return; using (var filestream = File.Create($"{savePath}{formFile.FileName}")) { await formFile.CopyToAsync(filestream); } }
- 修复静态字段注入问题
CompanyInfoService中使用静态字段_environment会导致多请求并发时的资源冲突,改为实例字段:
public class CompanyInfoService : ICompanyInfoService { private readonly IWebHostEnvironment _environment; public CompanyInfoService(IWebHostEnvironment environment) { _environment = environment; } // 其余代码保持不变 }
- 检查服务器文件路径权限
确保Ubuntu服务器上的目标保存目录具备应用程序运行用户(如www-data)的读写权限:
# 设置目录权限 chmod 755 /path/to/your/save/directory # 更改目录所属用户 chown www-data:www-data /path/to/your/save/directory
内容的提问来源于stack exchange,提问作者Akmal Bukhariev
相关产品推荐
相关产品推荐

