SWIFT TASK CONTINUATION MISUSE:泄露了其延续,在 FOR 循环后无法返回结果

SWIFT TASK CONTINUATION MISUSE: leaked its continuation and unable to return result after for loop

提问人:Narayana MV 提问时间:6/21/2023 最后编辑:Narayana MV 更新时间:7/6/2023 访问量:661

问:

我收到以下警告:

SWIFT 任务延续误用:saveAndClose() 泄露了其延续!

在for循环中,在执行了几个项目后,在其中一个项目中,在保存和关闭功能中,它被阻止,并显示上述错误,并且没有移动到下一个项目,因此无法返回结果。

这里可能有什么问题,有什么方法可以优化这些片段中的任何一个?

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] = [FTShelfTagsItem]()

     for case let item in items where item.documentUUID != nil {
         guard let docUUID = item.documentUUID else { continue }//, item.URL.downloadStatus() == .downloaded else { continue }
         let destinationURL = FTDocumentCache.shared.cachedLocation(for: docUUID)
         print(destinationURL.path)
         // move to post processing phace
         do {
             let document = await FTNoteshelfDocument(fileURL: destinationURL)
             let isOpen = try await document.openDocument(purpose: FTDocumentOpenPurpose.read)
             if isOpen {
                 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)
             _ = await document.saveAndClose()
         } catch {
             cacheLog(.error, error, destinationURL.lastPathComponent)
         }
     }
     cacheLog(.success, totalTagItems.count)
     let result = FTShelfTagsResult(tagsItems: totalTagItems)
     return result
 }


func saveAndClose() async -> Bool {
    return await withCheckedContinuation({ continuation in
        self.saveAndCloseWithCompletionHandler { isSuccess in
            continuation.resume(returning: isSuccess)
        }
    })
}

func saveAndCloseWithCompletionHandler(_ onCompletion :((Bool) -> Void)?)
{
    FTCLSLog("Doc: Save and Close");
    self.prepareForClosing();
    self.saveDocument { (saveSuccess) in
        if(saveSuccess) {
            self.closeDocument(completionHandler: { (_) in
                onCompletion?(saveSuccess);
            });
        }
        else {
            onCompletion?(saveSuccess);
        }
    }
}

    func saveDocument(completionHandler : ((Bool) -> Void)?)
    {
        if(self.openPurpose == .read) {
            FTLogError("Doc Saved in Readonly");
            completionHandler?(true);
            return;
        }
        if(self.hasAnyUnsavedChanges) {
            (self.delegate as? FTNoteshelfDocumentDelegate)?.documentWillStartSaving(self);
        }
        FTCLSLog("Doc: Save");
        #if !NS2_SIRI_APP
        self.recognitionCache?.saveRecognitionInfoToDisk(forcibly: true);
        if(self.hasAnyUnsavedChanges) {
            if let cache = self.recognitionCache, let cachePlist = cache.recognitionCachePlist() {
                let mutableDict = NSMutableDictionary.init(dictionary: cachePlist.contentDictionary);
                self.recognitionInfoPlist()?.updateContent(mutableDict);
            }
            if let cache = self.recognitionCache, let cachePlist = cache.visionRecognitionCachePlist() {
                let mutableDict = NSMutableDictionary.init(dictionary: cachePlist.contentDictionary);
                self.visionRecognitionInfoPlist()?.updateContent(mutableDict);
            }

            //This was added in version 6.2, when we removed the bounding rect from the Segment level storage.
            updateDocumentVersionToLatest()
        }
        #endif

        super.save { (success) in
            if(success) {
                self.previousFileModeificationDate = self.fileModificationDate;
                let pages = self.pages();
                for eachPage in pages {
                    eachPage.isDirty = false;
                }
            }
            completionHandler?(success);
        }
    }
    
    func closeDocument(completionHandler: ((Bool) -> Void)?)
    {
         FTCLSLog("Doc: Close");
        #if !NS2_SIRI_APP
        self.recognitionCache?.saveRecognitionInfoToDisk(forcibly: true)
        #endif
        super.close { (success) in
            self.removeObservers();
            completionHandler?(success);
        }
    }


需要修复以避免 -

SWIFT 任务延续误用:saveAndClose() 泄露了其延续!

并在执行 for 循环的所有项目后正确返回结果。

iOS Swift 步并 swift-并发

评论

1赞 lorem ipsum 6/21/2023
该错误通常意味着您要恢复两次,我的猜测是完成处理程序返回了两次
0赞 Larme 6/21/2023
stackoverflow.com/questions/68145462/......检查代码和/或共享它saveAndClose()
0赞 Narayana MV 6/21/2023
saveAndClose() 已经存在,请验证
1赞 Larme 6/21/2023
哦,对不起,它隐藏在卷轴中。现在显示 ?似乎处理程序可能会被多次调用,或者不会被调用一次。saveAndCloseWithCompletionHandler()
0赞 Narayana MV 6/22/2023
@Larme添加了 saveAndCloseWithCompletionHandler 的代码。

答:

0赞 Rob 6/23/2023 #1

请考虑以下错误:

SWIFT 任务延续误用:saveAndClose() 泄露了其延续!

这意味着您有一些从未调用其完成处理程序的执行路径,因此延续被泄露了。(这并不是说您调用完成处理程序的次数过多,因为这是不同的错误消息。

我没有看到明显的执行路径,您忽略了调用完成处理程序。到目前为止,您与我们共享的代码中似乎没有问题: 但是您也有自定义方法,这些方法具有自己的完成处理程序,例如 和 您尚未与我们共享,因此可能其中一个方法具有执行路径,其中无法调用其完成处理程序。因此,我们无法确切地说出问题出在您的案例中。但是,归根结底,您可能没有在某处调用完成处理程序。您可能需要插入一些日志记录语句和/或断点,并查看是否可以诊断出相关完成处理程序调用失败的位置。saveDocumentcloseDocument


考虑这个问题的这个可重现的例子。以下基于完成处理程序的例程,在 1 秒后返回某个数字的平方根(我只是模拟一些遗留的异步过程):

private func legacySquareRoot(of value: Double, completion: @escaping (Double) -> Void) {
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        if value >= 0 {
            completion(sqrt(value))
        }
    }
}

显然,我不想计算负数的平方根。但上述情况是不正确的,因为并非每个执行路径都会调用其完成处理程序。(这是我们更喜欢 Swift 并发而不是完成处理程序模式的原因之一,因为此问题不会在本机 Swift 并发代码中发生。

无论如何,让我们假设我们为这个遗留函数编写了一个包装器:async

func squareRoot(of value: Double) async -> Double {
    await withCheckedContinuation { continuation in
        legacySquareRoot(of: value) { result in
            continuation.resume(returning: result)
        }
    }
}

如果我用正值调用它,一切都很好:

let value = await experiment.squareRoot(of: 2)
print(value)                                    // 1.4142135623730951

但是,如果我用负值调用它,我会得到“泄露其延续”错误(因为我的旧函数有一个错误,只有在值为正时才调用闭包):

let value = await experiment.squareRoot(of: -2)
print(value)                                    // SWIFT TASK CONTINUATION MISUSE: squareRoot(of:) leaked its continuation!

显然,解决方法是每个执行路径都必须调用其完成处理程序。

评论

0赞 Narayana MV 7/6/2023
我也有用于保存和关闭文档功能的完成处理程序。现在编辑了我的帖子(也添加了这些功能)。如果你能找到任何帮助,你能看看吗?
0赞 Rob 7/7/2023
仍然没有向我跳出来。但未能恢复延续无疑是问题所在。也许或者可能没有在某些边缘情况下调用他们的完成处理程序?你必须一直追踪它。或者添加日志记录语句,以确定在什么情况下未调用闭包。或者只是重构所有代码以使用 async-await(而不仅仅是这个顶级方法),问题就消失了。super.savesuper.close