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

.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已关闭的异常。

修复步骤

  1. 修正异步方法的返回类型与调用方式
    将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);
   }
 } 
  1. 修复静态字段注入问题
    CompanyInfoService中使用静态字段_environment会导致多请求并发时的资源冲突,改为实例字段:
public class CompanyInfoService : ICompanyInfoService
{
  private readonly IWebHostEnvironment _environment;
  public CompanyInfoService(IWebHostEnvironment environment)
  {
      _environment = environment;
  }

  // 其余代码保持不变
}
  1. 检查服务器文件路径权限
    确保Ubuntu服务器上的目标保存目录具备应用程序运行用户(如www-data)的读写权限:
# 设置目录权限
chmod 755 /path/to/your/save/directory
# 更改目录所属用户
chown www-data:www-data /path/to/your/save/directory

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 01:15:06