Web界面批量删除属性异步写入异常及同步实现方案咨询
解决批量删除属性时的异步冲突问题
你遇到的问题核心是并行异步请求导致的文件读写竞态:循环里的每个删除请求几乎同时发起,它们都会读取原始的属性文件内容,各自删除目标项后再写回文件。后面的请求会覆盖前面的修改,最终导致结果混乱——这也是为什么你用.then()没达到预期,因为.then()只是单个请求完成后的回调,并没有让多个请求串行执行。
下面给你几个可行的解决方案,按改动成本从低到高排序:
方案一:前端改成串行执行(改动最小)
把循环里的并行请求改成逐个发起,上一个删除完成后再执行下一个。用async/await可以很方便实现:
async deleteSelected() { // 遍历选中的属性ID,逐个等待删除完成 for (const propertyId of this.selectedProperties) { await this.metadataPropertyService.deleteProperty(this.packageName, propertyId); } // 所有删除完成后再刷新数据 this.ngOnChanges(); }
这样每个请求都会基于最新的文件内容操作,彻底避免竞态问题,而且不需要修改后端代码,快速见效。唯一的小缺点是如果选中大量属性,总耗时会是单个请求的N倍,但对于一般的业务场景完全够用。
方案二:后端批量处理(性能最优)
如果想进一步提升效率,可以让前端一次性把所有要删除的ID传给后端,后端一次性完成删除和写入操作,只需要一次文件读写:
前端修改
deleteSelected() { // 把选中的属性ID数组一次性传给后端 this.metadataPropertyService.deleteProperties(this.packageName, this.selectedProperties) .then(() => { this.ngOnChanges(); }); }
后端新增接口
public IData deleteProperties(IData pipeline){ // 解析批量删除的输入参数 PropertyBatchInput input = IDataToObjectParser.getDeletePropertiesInput(pipeline); // 读取当前属性列表 List<Property> properties = new ArrayList<>(getProperties(input.getPackageName())); // 批量移除所有匹配的属性ID properties.removeIf(property -> input.getIds().contains(property.getId())); // 一次性写入修改后的列表 return writeProperties(FileUtils.getFile(input.getPackageName(), configurations), properties); }
这种方式只需要一次网络请求和一次文件IO,性能最好,也从根源上消除了竞态问题,适合需要处理大量删除操作的场景。
方案三:后端加文件锁(适合无法修改前端的场景)
如果没办法修改前端代码,可以在后端的文件读写逻辑上加锁,确保同一时间只有一个请求能修改属性文件:
// 定义一个全局锁对象,按包名区分锁(避免不同包之间互相阻塞) private final Map<String, Object> packageLocks = new ConcurrentHashMap<>(); public IData deleteProperty(IData pipeline){ PropertyInput propertyInput = IDataToObjectParser.getDeletePropertyInput(pipeline); String packageName = propertyInput.getPackageName(); // 获取当前包对应的锁,没有则创建 Object lock = packageLocks.computeIfAbsent(packageName, k -> new Object()); synchronized(lock) { // 加锁后再执行读写操作 List<Property> properties = new ArrayList<>(getProperties(packageName)); Property property = getProperty(properties, propertyInput.getId()); if(properties.remove(property)){ return writeProperties(FileUtils.getFile(packageName, configurations), properties); } Error error = new Error("MTD-WR-01", "TECHNICAL","Cannot remove property with id " + propertyInput.getId()); return ObjectToIDataParser.getStatus(new Status(State.ERROR, error)); } }
这个方案不需要修改前端,但要注意锁的粒度(按包名加锁比全局锁更合理),避免不必要的性能损耗。
总结
优先推荐方案一,改动最小、快速解决问题;如果对性能有要求,就用方案二;只有在无法修改前端的情况下,再考虑方案三。
内容的提问来源于stack exchange,提问作者D.Dsn
相关产品推荐
相关产品推荐

