调用NSUrlSession的reset(completionHandler:)时的任务处理逻辑及等待实现方法
reset(completionHandler:)对运行中任务的实际处理逻辑 只要你在URLSession上调用了这个接口,会话里所有还没执行完的任务(不管是正在传输、还是被暂停挂起等待恢复的状态)都会被立刻强制取消,根本不会给任务留跑完的机会。
整个reset的内部执行顺序是固定的:先把所有关联的活跃任务全部终止,再依次清理会话持有的所有本地资源——包括内存/磁盘缓存、存储的认证凭证、Cookie、自定义协议层的缓存数据、下载任务的临时断点文件,等所有内部状态完全清空、没有残留的任务句柄之后,才会触发你传入的completionHandler回调。
不管是普通数据任务、大文件上传任务、下载任务,只要是这个会话持有的,都会被一刀切取消,哪怕任务已经传输到最后1%,也不会自动保留结果或者支持断点恢复。
等待所有现有任务完成后再执行reset的实现方案
想要避免任务被reset强制取消,核心逻辑是在reset执行前做好任务状态追踪,等所有活跃任务自然结束后再触发接口调用,具体落地步骤:
- 维护原子性的活跃任务计数器。每次给会话提交新任务时,对活跃计数做累加操作;在每个任务的全场景完成回调(成功、失败、主动取消都会触发)里对计数做递减。因为任务回调可能在不同的系统队列触发,所有计数的读写都要通过串行队列或者原子属性做保护,避免多线程竞态导致计数不准。
- 新增会话待重置状态标记。当外部收到reset触发指令时,先把会话标记为待重置状态,标记生效后立刻停止接收所有新的任务提交,新收到的任务直接取消,不要进入会话队列。
- 任务完成时的触发判断。在每个任务的完成回调里,计数递减完成后立刻检查两个前提:当前会话是不是处于待重置状态、活跃任务计数是不是已经降到0。两个条件同时满足时,再正式调用
reset(completionHandler:)接口。 - 避坑提醒:不要用
getTasksWithCompletionHandler:轮询判断任务是否结束。这种方式不仅会额外占用会话资源,还很容易出现“轮询时查到所有任务都结束了,轮询的间隙又有新任务被提交进来”的竞态,最终导致reset在任务还在运行时被触发。
可以参考下面的简化实现示例:
// 会话状态标记 private var isPendingReset = false // 任务追踪用的串行队列,保护计数操作 private let taskTrackQueue = DispatchQueue(label: "session.task.tracker") private var activeTaskCount = 0 private var session: URLSession! // 提交新任务的方法 func enqueueTask(_ task: URLSessionTask) { // 待重置状态下直接拒绝新任务 guard !isPendingReset else { task.cancel() return } taskTrackQueue.sync { activeTaskCount += 1 } // 绑定任务完成回调,做计数递减和状态检查 task.completionHandler = { [weak self] in guard let self = self else { return } self.taskTrackQueue.async { self.activeTaskCount -= 1 // 满足待重置+无活跃任务的条件,就触发reset if self.activeTaskCount == 0, self.isPendingReset { self.session.reset { // 这里写reset完成后的后续逻辑 } } } } task.resume() } // 外部调用reset的入口 func requestReset() { taskTrackQueue.async { self.isPendingReset = true // 触发时如果已经没有活跃任务,直接执行reset if self.activeTaskCount == 0 { self.session.reset { // reset完成逻辑 } } } }
补充说明:如果使用的是后台类型的URLSession,需要额外同步系统侧的任务完成事件——因为后台会话的任务哪怕App退到后台,也可能被系统调度继续运行,仅靠前台的任务回调计数会出现漏算,需要结合URLSessionDelegate的相关回调同步计数,保证状态判断准确。
内容的提问来源于stack exchange,提问作者mango
相关产品推荐
相关产品推荐

