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

Swift Task Continuation泄漏问题排查及代码优化求助

问题根源分析

1. Continuation未被正确Resume(核心触发原因)

saveAndClose()中使用的withCheckedContinuation要求所有代码路径必须调用resume,但当前代码存在以下场景会导致continuation永久挂起:

  • 若saveDocument或closeDocument底层逻辑异常(如文件锁死、资源占用),其completion handler未触发,onCompletion就不会执行,continuation永远无法resume,触发泄漏警告。
  • 当文档未成功打开(isOpen == false)时,仍调用saveAndClose(),此时文档处于未初始化状态,后续操作可能卡住,无法触发onCompletion。

2. 串行循环的阻塞风险

当前循环为串行执行,某一项的saveAndClose()阻塞会导致整个流程停滞,后续所有项无法执行。


修复方案

方案1:确保Continuation在所有场景下被Resume

修改saveAndClose(),添加超时兜底和弱引用保护,避免continuation泄漏:

func saveAndClose() async -> Bool {
    return await withCheckedContinuation { [weak self] continuation in
        guard let self = self else {
            continuation.resume(returning: false)
            return
        }
        
        var didResume = false
        
        // 超时兜底:10秒后强制resume,避免永久挂起
        DispatchQueue.global().asyncAfter(deadline: .now() + 10) {
            if !didResume {
                didResume = true
                continuation.resume(returning: false)
            }
        }
        
        self.saveAndCloseWithCompletionHandler { isSuccess in
            if !didResume {
                didResume = true
                continuation.resume(returning: isSuccess)
            }
        }
    }
}

同时检查saveDocument和closeDocument的所有分支,确保completionHandler一定会被调用(比如super.save/super.close的底层实现是否存在异常不回调的情况,必要时添加兜底)。

方案2:优化循环中的文档处理逻辑

仅在文档成功打开时执行saveAndClose(),避免对未初始化文档执行无效操作:

private func processTags(reqItems: [FTShelfItemProtocol], selectedTags: [String]) async throws -> FTShelfTagsResult { 
    let items: [FTDocumentItemProtocol] = reqItems.filter({ ($0.URL.downloadStatus() == .downloaded) }).compactMap({ $0 as? FTDocumentItemProtocol })

    var totalTagItems: [FTShelfTagsItem] = []

    for item in items where item.documentUUID != nil {
        guard let docUUID = item.documentUUID else { continue }
        let destinationURL = FTDocumentCache.shared.cachedLocation(for: docUUID)
        
        do {
            let document = await FTNoteshelfDocument(fileURL: destinationURL)
            let isOpen = try await document.openDocument(purpose: .read)
            
            var needClose = false
            if isOpen {
                needClose = true
                let tags = await document.documentTags()
                let considerForResult = selectedTags.allSatisfy(tags.contains(_:))
                if considerForResult && !tags.isEmpty {
                    var tagsBook = FTShelfTagsItem(shelfItem: item, type: .book)
                    tagsBook.tags = tags
                    totalTagItems.append(tagsBook)
                }
            }

            let tagsPage = await document.fetchSearchTagsPages(shelfItem: item, selectedTags: selectedTags)
            totalTagItems.append(contentsOf: tagsPage)
            
            // 仅在文档成功打开时执行关闭操作
            if needClose {
                _ = await document.saveAndClose()
            }
        } catch {
            cacheLog(.error, error, destinationURL.lastPathComponent)
        }
    }
    
    cacheLog(.success, totalTagItems.count)
    return FTShelfTagsResult(tagsItems: totalTagItems)
}

方案3:并行处理循环任务(可选,提升性能)

如果业务允许,将串行循环改为并行处理,避免单个任务阻塞整个流程:

private func processTags(reqItems: [FTShelfItemProtocol], selectedTags: [String]) async throws -> FTShelfTagsResult { 
    let items: [FTDocumentItemProtocol] = reqItems.filter({ ($0.URL.downloadStatus() == .downloaded) }).compactMap({ $0 as? FTDocumentItemProtocol })

    // 并行处理每个文档
    let tagItemsArrays = await withTaskGroup(of: [FTShelfTagsItem].self) { group in
        for item in items where item.documentUUID != nil {
            guard let docUUID = item.documentUUID else { continue }
            let destinationURL = FTDocumentCache.shared.cachedLocation(for: docUUID)
            
            group.addTask {
                var tagItems: [FTShelfTagsItem] = []
                do {
                    let document = await FTNoteshelfDocument(fileURL: destinationURL)
                    let isOpen = try await document.openDocument(purpose: .read)
                    
                    var needClose = false
                    if isOpen {
                        needClose = true
                        let tags = await document.documentTags()
                        let considerForResult = selectedTags.allSatisfy(tags.contains(_:))
                        if considerForResult && !tags.isEmpty {
                            var tagsBook = FTShelfTagsItem(shelfItem: item, type: .book)
                            tagsBook.tags = tags
                            tagItems.append(tagsBook)
                        }
                    }

                    let tagsPage = await document.fetchSearchTagsPages(shelfItem: item, selectedTags: selectedTags)
                    tagItems.append(contentsOf: tagsPage)
                    
                    if needClose {
                        _ = await document.saveAndClose()
                    }
                } catch {
                    cacheLog(.error, error, destinationURL.lastPathComponent)
                }
                return tagItems
            }
        }
        
        // 收集所有并行任务的结果
        var allTagItems: [FTShelfTagsItem] = []
        for await result in group {
            allTagItems.append(contentsOf: result)
        }
        return allTagItems
    }
    
    cacheLog(.success, tagItemsArrays.count)
    return FTShelfTagsResult(tagsItems: tagItemsArrays)
}

额外注意事项
  • 检查FTNoteshelfDocument的生命周期,使用弱引用避免循环引用导致实例无法释放;
  • 为文件操作添加详细日志,便于定位具体是哪个文档导致的阻塞;
  • 在saveAndClose中提前判断openPurpose,只读模式下跳过保存步骤,减少无效操作。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 04:32:05