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

.NET 6大文件处理与令牌过期:资源释放及内存泄漏问题咨询

.NET 6大文件上传内存泄漏与资源释放问题

问题背景

通过Android设备向.NET 6 Web API上传100MB文件时,请求返回StatusCodes.Status401Unauthorized,担心Web API接收的大文件是否会被正确释放。

Android端已实现上传失败重试逻辑,但发现服务器内存持续飙升无法回落,最终导致服务器挂起崩溃。希望了解:

  • .NET 6在这类场景下的内部资源处理机制,是否需要开发者手动释放文件资源
  • 除401外,其他错误场景下的资源回收流程

Android端上传代码

private void runAnnotationBackup(SharedPreferences cooperatePrefs){
    try {
        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... voids) {
                List<Version> versions = versionViewModel.getBackupNeededAnnotationsData();
                if(versions!=null && !isAnnotationUploadRunning){
                    if(versions.size()==0) return null;
                    String pdfDirPath = Constants.getUserOriginalAnnotationDirectory(UserInitActivity.this, user);
                    File annotationsFolder = new File(pdfDirPath);
                    if(!annotationsFolder.isDirectory()) return null;
                    isAnnotationUploadRunning = true;
                    FileFilter fileFilter = new WildcardFileFilter("*.xfdf");
                    File[] files = annotationsFolder.listFiles(fileFilter);
                    for (Version ver : versions) {
                        for (File file : files) {
                            String fileName = file.getName();
                            String[] fileData = fileName.split("_");
                            String filePaperId = fileData[0];
                            String versionId = fileData[2].substring(0, fileData[2].length() - 5);
                            if(ver.getPaperId() == Integer.parseInt(filePaperId) && ver.getIsAnnotBackupNeeded() == 1){
                                File reqfile = new File(file.getPath());
                                MultipartBody.Part[] fileParts = new MultipartBody.Part[1];
                                RequestBody fileBody = RequestBody.create(MediaType.parse("application/octet-stream"), reqfile);
                                fileParts[0] = MultipartBody.Part.createFormData("files", reqfile.getName(), fileBody);
                                SecureRandom random = new SecureRandom();
                                int randomInt = random.nextInt(1000000000);
                                SharedPreferences sharedPreferencesV4User = UserInitActivity.this.getSharedPreferences(SharedPrefDetails.getSharedPreferencesV4User(UserInitActivity.this, "222"), Context.MODE_PRIVATE);
                                String loginResponseAccessToken = sharedPreferencesV4User.getString("LoginResponseAccessToken", null);
                                AnnotationShareRequest annotationShareRequest = new AnnotationShareRequest();
                                annotationShareRequest.setPaperVersionId(Integer.parseInt(versionId));
                                annotationShareRequest.setUniqueIdDevice(randomInt);
                                Gson gson = new Gson();
                                String annotationPart = gson.toJson(annotationShareRequest);
                                RequestBody annotationData = RequestBody.create(MediaType.parse("text/plain"), annotationPart);
                                Call<Void> annotationBackupRequest = bpmainapi.uploadAnnotationsForPaparV4("Bearer " + loginResponseAccessToken,filePaperId,filePaperId,"5",annotationData,fileParts);
                                annotationBackupRequest.enqueue(new Callback<Void>() {
                                    @Override
                                    public void onResponse(Call<Void> call, Response<Void> response) {
                                        if (response.code()==200) {
                                            Version paperVersion = new Version();
                                            paperVersion.setVersionId(Integer.parseInt(versionId));
                                            paperVersion.setPaperId(Integer.parseInt(filePaperId));
                                            paperVersion.setAnnotBackupStatus(0);
                                            paperVersion.setIsAnnotBackupNeeded(BoardPACEnumsReference.AnnotBackup.NoNeed);
                                            paperVersion.setCurrentPage(0);
                                            paperVersion.setCreatedDate(new Date());
                                            new AsyncTask<Void, Void, Void>() {
                                                @Override
                                                protected Void doInBackground(Void... voids) {
                                                    versionViewModel.updateAnnotationBackupNeededForPaper(paperVersion);
                                                    isAnnotationUploadRunning = false;
                                                    cooperatePrefs.edit().putString(SharedPreferencesCooperate.lastAnnotationBackupDate, SettingsCache.getInstance().getServerTime()).apply();
                                                    return null;
                                                }
                                            }.execute();
                                        } else {
                                            isAnnotationUploadRunning = false;
                                        }
                                    }
                                    @Override
                                    public void onFailure(Call<Void> call, Throwable t) {
                                        isAnnotationUploadRunning = false;
                                    }
                                });
                            }else{
                                isAnnotationUploadRunning = false;
                            }
                        }
                    }
                }
                return null;
            }
        }.execute();
    }catch (Exception e){}
}

.NET 6 Web API接收代码

[HttpPost("{id}/backup-annotations")]
[RefIdValidator("id")]
public async Task<IActionResult> BackupAnnotationsAsync(int id, [ModelBinder(typeof(JsonWithFilesFormDataModelBinder), Name = "annotation")][Required] AnnotationModel model, IFormFileCollection files)
{
    if (files.Count == 0) return Problem(statusCode: 400, title: "No annotations found to backup");
    if (files.Count > 1) return Problem(statusCode: 400, title: "Only annotations can be backed up, not paper and annotations");

    model.PaperId = id;
    model.CreatedOn = DateTime.UtcNow; //Do not remove, used for encryption
    await _annotationService.BackupAnnotationsAsync(model, files);

    return Ok();
}

问题解答

1. .NET 6对IFormFile的资源处理机制

.NET 6中,IFormFile和IFormFileCollection的资源由框架自动管理:

  • 当请求处理完成(无论成功还是失败,包括401、400等错误场景),框架会自动释放IFormFile对应的文件流资源
  • 默认情况下,上传的文件会先被写入服务器的临时目录(由ASPNETCORE_TEMP环境变量指定),请求结束后框架会自动清理这些临时文件

2. 内存飙升的可能原因及解决方案

内存持续飙升大概率是重试逻辑导致短时间内大量大文件请求同时到达,框架来不及及时清理临时文件或回收内存,可采取以下优化措施:

  • 优化重试策略:Android端添加指数退避重试间隔(比如第一次等1秒,第二次等2秒,依此类推),避免短时间内大量请求冲击服务器
  • 调整上传配置:在Program.cs中配置禁用内存缓存,强制大文件写入临时文件:
    builder.Services.Configure<FormOptions>(options =>
    {
        options.MultipartBodyLengthLimit = 100 * 1024 * 1024; // 100MB
        options.MultipartMemoryThreshold = 0; // 禁用内存缓存
    });
    
  • 提前拦截未授权请求:将权限验证逻辑移到AuthorizationFilter中,确保在接收完整文件前就拦截401请求,减少不必要的文件接收
  • 检查服务层代码:确认_annotationService.BackupAnnotationsAsync中是否正确处理文件流,若手动打开流需确保及时关闭(CopyToAsync内部会自动处理流释放)

3. 无需手动释放的场景

只要没有在代码中手动保留IFormFile的引用(比如存储在静态变量、全局缓存中),框架会在请求生命周期结束后自动完成资源回收,无需开发者手动调用Dispose方法。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 04:24:57