我正在尝试扩展我class
的async/await
功能,但在运行时控制台中出现错误:
SWIFT TASK CONTINUATION MISUSE: query(_:) leaked its continuation!
Run Code Online (Sandbox Code Playgroud)
下面是class
我正在尝试添加使用委托的延续:
SWIFT TASK CONTINUATION MISUSE: query(_:) leaked its continuation!
Run Code Online (Sandbox Code Playgroud)
这是我如何使用它:
class LocalSearch: NSObject, MKLocalSearchCompleterDelegate {
private let completer: MKLocalSearchCompleter
private var completionContinuation: CheckedContinuation<[MKLocalSearchCompletion], Error>?
init() {
completer = MKLocalSearchCompleter()
super.init()
completer.delegate = self
}
func query(_ value: String) async throws -> [MKLocalSearchCompletion] {
try await withCheckedThrowingContinuation { continuation in
completionContinuation = continuation
guard !value.isEmpty else {
completionContinuation?.resume(returning: [])
completionContinuation = nil
return
}
completer.queryFragment = value
}
}
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
completionContinuation?.resume(returning: completer.results)
completionContinuation = nil
}
func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
completionContinuation?.resume(throwing: error)
completionContinuation = nil
}
}
Run Code Online (Sandbox Code Playgroud)
我做错了什么还是有更好的方法来实现这一目标?
Cri*_*tik 47
withCheckedContinuation
如果您通过、 或创建的延续withCheckedThrowingContinuation
在被丢弃之前未报告成功或失败,则会出现此消息。这会导致资源泄漏:
从延续中多次恢复是未定义的行为。永不恢复会使任务无限期地处于挂起状态,并泄漏任何关联的资源。如果违反这些不变量中的任何一个,CheckedContinuation 会记录一条消息。
摘自(强调我的)文档CheckedContinuation
。
问题不仅仅是存在一些资源泄漏,永远不恢复延续意味着某些await
调用将永远挂起,这取决于您的项目架构,这可能或多或少有问题。
以下是发生这种情况的可能原因:
if
退出作用域而不指示延续报告成功/失败guard
case
class Searcher {
func search(for query: String) async throws -> [String] {
await withCheckedContinuation { continuation in
someFunctionCall(withCompletion: { [weak self] in
guard let `self` = self else {
// if `result` doesn't have the expected value, the continuation
// will never report completion
return
}
continuation.resume(returning: something)
})
}
}
}
Run Code Online (Sandbox Code Playgroud)
class Searcher {
private let internalSearcher = InternalSearcher()
func search(for query: String) async throws -> [String] {
await withCheckedContinuation { continuation in
internalSearcher.search(query: query) { result in
// everything fine here
continuation.resume(returning: result)
}
}
}
}
class InternalSearcher {
func search(query: String, completion: @escaping ([String]) -> Void {
guard !query.isEmpty else {
return
// legit precondition check, however in this case,
// the completion is not called, meaning that the
// upstream function call will imediately discard
// the continuation, without instructing it to report completion
}
// perform the actual search, report the results
}
}
Run Code Online (Sandbox Code Playgroud)
class Searcher {
var continuation: CheckedContinuation<[String], Error>?
func search(for query: String) async throws -> [String] {
try await withCheckedTrowingContinuation { continuation in
// note how a second call to `search` will overwrite the
// previous continuation, in case the delegate method was
// not yet called
self.continuation = continuation
// trigger the searching mechanism
}
}
func delegateMethod(results: [String]) {
self.continuation.resume(returning: results)
self.continuation = nil
}
}
Run Code Online (Sandbox Code Playgroud)
#1 和 #2 通常发生在处理接受完成回调的函数时,而 #3 通常发生在处理委托方法时,因为在这种情况下,我们需要将延续存储在异步函数范围之外的某个位置,以便访问它来自委托方法。
底线 - 尝试确保延续报告所有可能的代码路径上的完成情况,否则,延续将无限期地阻止异步调用,导致与该异步调用关联的任务泄漏其关联资源。
在您的情况下,可能发生的情况是在query()
第一个呼叫有机会完成之前发生了第二个呼叫。
在这种情况下,第一个延续被丢弃而没有报告完成,这意味着第一个调用者在调用后从未继续执行try await query()
,这是完全不行的。
需要修复以下代码,以免覆盖挂起的延续:
func query(_ value: String) async throws -> [MKLocalSearchCompletion] {
try await withCheckedThrowingContinuation { continuation in
completionContinuation = continuation
Run Code Online (Sandbox Code Playgroud)
一个快速的解决方案是存储一组延续,恢复委托方法中的所有延续,然后清除数组。此外,在您的特定情况下,您可以简单地从延续代码中提取验证,因为即使在异步函数中,您也可以同步返回/抛出:
func query(_ value: String) async throws -> [MKLocalSearchCompletion] {
guard !value.isEmpty else {
return []
}
return try await withCheckedThrowingContinuation { continuation in
continuations.append(continuation)
completer.queryFragment = value
}
}
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
continuations.forEach { $0.resume(returning: completer.results) }
continuations.removeAll()
}
func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
continuations.forEach { $0.resume(throwing: error) }
continuations.removeAll()
}
Run Code Online (Sandbox Code Playgroud)
我还强烈建议将您的类转换为演员,以避免数据竞争,无论您是否像现在一样存储一个延续,或者使用数组。原因是连续属性是由多个线程消耗的,在某些时候,您可能最终会出现两个线程同时访问/写入该属性。
Tar*_*agi -1
我认为问题就在这里——
func query(_ value: String) async throws -> [MKLocalSearchCompletion] {
try await withCheckedThrowingContinuation { continuation in
// storing into a variable makes this continuation instance outlive the scope of it
// In other words, it leaks OR escapes the scope
// This is same as why we need to add @escaping attribute for callback functions arguments
// those are either stored in variables like this
// or passed to other functions (escaping scope of current function)
completionContinuation = continuation
// Try commenting above line, the warning should go away
// And your code will stop working as well :)
// How to design this component is other question.
}
}
Run Code Online (Sandbox Code Playgroud)
import MapKit
class LocalSearch: NSObject, MKLocalSearchCompleterDelegate {
typealias Completion = (_ results: [MKLocalSearchCompletion]?, _ error: Error?) -> Void
private let completer: MKLocalSearchCompleter
private var completion: Completion?
override init() {
completer = MKLocalSearchCompleter()
super.init()
completer.delegate = self
}
func query(_ value: String, completion: @escaping Completion) {
self.completion = completion
completer.queryFragment = value
}
func query(_ value: String) async throws -> [MKLocalSearchCompletion] {
try await withCheckedThrowingContinuation { continuation in
guard !value.isEmpty else {
continuation.resume(returning: [])
return
}
self.query(value, completion: { (results, error) in
if let error = error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: results ?? [])
}
})
}
}
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
completion?(completer.results, nil)
}
func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
completion?(nil, error)
}
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
202 次 |
最近记录: |