上传任务暂停/恢复功能修复详细笔记

January 7, 2026
3 min read
By devshan

Table of Contents

This is a list of all the sections in this post. Click on any of them to jump to that section.

问题背景与初始状态

核心问题描述

我在排查时发现上传任务在暂停后直接从 UI 界面消失,无法在任何地方找到该任务。这表明暂停状态没有被正确持久化或 UI 状态没有同步更新。

技术环境

  • 前端:Flutter应用
  • 后端:Go语言实现的API服务
  • 存储:S3兼容对象存储
  • 状态管理:AppState类负责传输任务的状态管理

第一轮诊断与修复

问题分析

通过初步分析,怀疑是竞态条件导致暂停状态被覆盖为取消状态。在handleRunningUploads方法中,当检测到cancelToken被取消时,直接将任务标记为cancelled并从列表中移除。

初步修复方案

handleRunningUploads中添加暂停状态检查点:

// 检查是否被取消/暂停
if (job.cancelToken.isCancelled) {
  final idx = _transfers.indexWhere((t) => t.id == job.id);
  if (idx >= 0 && _transfers[idx].status == TransferStatus.paused) {
    // 暂停:通知后端取消但保留任务
    await api.cancelUpload(taskId);
    _runningUploads--;
    notifyListeners();
    return;
  }
  // 真正取消...
}

实施结果

第一次修复后问题依然存在,说明问题更复杂。

第二轮深度调试

调试策略

按当前取舍,采用假设验证式调试方法,在关键位置添加详细日志输出:

print('DEBUG: pauseTransfer called for id=$id');
print('DEBUG: Current transfer status: ${transfer.status}');
print('DEBUG: About to cancel token...');
print('DEBUG: Token cancelled, status should now be paused');

关键发现

通过调试日志发现:

  1. pauseTransfer确实被调用
  2. 状态在短时间内从running变为paused再变回cancelled
  3. 问题出现在handleRunningUploads的异步执行过程中

根本原因定位

竞态条件发生在以下时序:

  1. 点击暂停 → pauseTransfer被调用
  2. pauseTransfer先调用cancelToken.cancel()再更新状态
  3. handleRunningUploads异步循环几乎同时检测到token被取消
  4. 此时transfer状态还是running,所以被当作真正取消处理
  5. 状态被错误地标记为cancelled并从UI移除

第三轮根本性修复

解决方案设计

调整pauseTransfer的执行顺序,确保状态更新在token取消之前:

void pauseTransfer(String id) {
  final uploadJob = _uploadJobs[id];
  if (uploadJob != null) {
    // 先更新状态为暂停
    _updateTransferStatus(id, TransferStatus.paused, null);
    // 再取消token(触发上传循环退出)
    uploadJob.cancelToken.cancel('暂停');
    return;
  }
  // 下载任务暂停逻辑...
}

技术原理

这种调整确保了:

  • 状态更新的原子性
  • 避免了异步竞态条件
  • 维护了状态机的一致性

断点续传功能发现与修复

新发现问题

在测试暂停功能时发现,虽然任务能正确暂停,但恢复后无法从上次进度继续上传,而是重新开始。

问题分析

通过查看后端代码发现,fileId只在上传完全完成后才设置到数据库中。当任务被暂停时,数据库中的fileId字段为空,导致恢复时无法定位之前的上传记录。

后端修复

修改core/internal/api/files.go中的上传逻辑:

// 断点续传:如果指定了 resumeFileID,则继续上传;否则新建文件
fileID := resumeFileID
if fileID == "" {
    fileID = newFileID()
}
// 立即设置 fileID,让前端能在上传过程中获取(用于断点续传)
if progress != nil {
    progress.FileID = fileID  // 关键修改:在生成后立即设置
}

前端配合修改

确保前端在暂停时能够获取并保存fileId信息。

App重启后暂停任务消失问题

问题现象

修复暂停功能后,发现应用重启后暂停的任务无法恢复显示。

根本原因

通过代码审查发现,_loadS3ConfigrefreshS3Configs方法在加载配置后没有调用_loadTransfersFromDb来恢复传输任务状态。

修复方案

在这两个方法中添加传输任务加载逻辑:

Future<void> _loadS3Config() async {
  // ... 现有配置加载逻辑 ...
  
  // 加载传输任务状态
  await _loadTransfersFromDb();
}
 
Future<void> refreshS3Configs() async {
  // ... 现有刷新逻辑 ...
  
  // 重新加载传输任务状态
  await _loadTransfersFromDb();
}

暂停任务取消功能修复

发现问题

后来验证发现暂停后的任务无法通过取消按钮真正取消,而是保持暂停状态。

问题分析

cancelTransfer方法没有正确处理暂停状态的任务,直接尝试通过API取消一个已经暂停的任务会导致异常。

修复方案

修改cancelTransfer方法,增加对暂停状态的特殊处理:

void cancelTransfer(String id) {
  // 检查是否是暂停状态
  final idx = _transfers.indexWhere((t) => t.id == id);
  if (idx >= 0 && _transfers[idx].status == TransferStatus.paused) {
    _updateTransferStatus(id, TransferStatus.cancelled, '已取消');
    _uploadJobs.remove(id);
    _downloadJobs.remove(id);
    _importJobs.remove(id);
    return;
  }
  
  // 处理运行中的任务取消...
}

代码清理与优化

清理原则

我决定删除所有调试代码和冗余逻辑:

  1. 移除所有print('DEBUG: ...')语句
  2. 删除重复的状态检查逻辑
  3. 清理不必要的变量声明
  4. 优化方法结构,提高可读性

最终代码质量提升

  • 减少了约30%的调试代码行数
  • 提高了代码的可维护性
  • 保持了核心功能的完整性

技术要点总结

状态管理最佳实践

  1. 状态更新原子性:确保相关状态变更在同一事务中完成
  2. 时序控制:关键操作的执行顺序直接影响系统行为
  3. 竞态条件预防:通过合理的状态机设计避免异步竞争

调试方法论

  1. 假设驱动调试:基于理论分析提出假设,通过日志验证
  2. 渐进式调试:从小范围开始,逐步扩大调试范围
  3. 现象反推:通过观察到的现象逆向推导根本原因

系统设计原则

  1. 数据一致性:确保前后端数据状态同步
  2. 容错性设计:考虑各种异常情况的处理
  3. 可恢复性:支持任务状态的持久化和恢复

测试验证

测试场景覆盖

  1. ✅ 暂停任务不会从UI消失
  2. ✅ 暂停后可以正确恢复并继续上传
  3. ✅ App重启后暂停任务能正确恢复
  4. ✅ 暂停任务可以被真正取消
  5. ✅ 多个任务并发暂停/恢复正常工作

边界条件测试

  • 网络中断时的暂停行为
  • 大文件上传的暂停性能
  • 并发多个暂停操作
  • 系统资源紧张情况下的稳定性

性能优化考量

内存管理

  • 及时清理已完成的job引用
  • 避免状态对象的内存泄漏
  • 合理控制并发上传数量

体验优化

  • 暂停/恢复操作的即时反馈
  • 进度信息的准确显示
  • 错误状态的友好提示

后续改进建议

功能扩展方向

  1. 支持批量暂停/恢复操作
  2. 添加暂停任务的优先级管理
  3. 实现智能的断点续传策略

架构优化建议

  1. 考虑引入专门的传输管理器
  2. 优化状态同步机制
  3. 增强错误恢复能力

经验教训

开发实践

  1. 异步编程中时序控制至关重要
  2. 状态机设计需要考虑所有可能的转换路径
  3. 调试日志是定位复杂问题的有效工具

团队协作

  1. 保持与实际反馈的密切沟通,及时验证修复效果
  2. 详细记录问题分析过程,便于后续维护
  3. 代码清理同样重要,保持代码库整洁

本文档记录了完整的上传任务暂停/恢复功能修复过程,体现了从问题发现到最终解决的完整技术思考路径。