如何取消具有从“async”操作启动返回的可取消类型的“async”函数

Dan*_*iel 6 cancellation async-await swift

我需要支持取消一个函数,该函数返回一个可以在启动后取消的对象。就我而言,该类requester位于我无法修改的第三方库中。

actor MyActor {

    ...

    func doSomething() async throws -> ResultData {

        var requestHandle: Handle?
    
        return try await withTaskCancellationHandler {
            requestHandle?.cancel() // COMPILE ERROR: "Reference to captured var 'requestHandle' in concurrently-executing code"
        } operation: {

            return try await withCheckedThrowingContinuation{ continuation in
            
                requestHandle = requester.start() { result, error in
            
                    if let error = error
                        continuation.resume(throwing: error)
                    } else {
                        let myResultData = ResultData(result)
                        continuation.resume(returning: myResultData)
                    }
                }
            }
        }
    }

    ...
}
Run Code Online (Sandbox Code Playgroud)

我已经审查了其他问题和此线程:https://forums.swift.org/t/how-to-use-withtaskcancellationhandler-properly/54341/4

有些案例非常相似,但又不完全相同。由于以下错误,此代码将无法编译:

“在并发执行的代码中引用捕获的 var 'requestHandle'”

我假设编译器试图在requestHandle初始化之前保护我不使用它。但我不确定还有什么办法可以解决这个问题。Swift 论坛讨论线程中显示的其他示例似乎都有一种模式,requester可以在调用其函数之前初始化对象start

我还尝试将其保存requestHandle为类变量,但在同一位置出现了不同的编译错误:

无法从 Sendable 闭包引用 Actor 隔离属性“profileHandle”

Rob*_*Rob 8

你说:

\n
\n

我假设编译器试图保护我不使用requestHandle它初始化之前的\xe2\x80\x99s。

\n
\n

或者,更准确地说,它只是保护您免受竞争。您需要同步与您的 \xe2\x80\x9crequester\xe2\x80\x9d 的交互以及Handle.

\n
\n

但我\xe2\x80\x99m 不知道如何解决这个问题。Swift 论坛讨论线程中显示的其他示例似乎都有一种模式,requester可以在调用其函数之前初始化对象start

\n
\n

是的,这正是你应该做的。不幸的是,您没有\xe2\x80\x99t 分享您的requester初始化位置或实现方式,因此我们很难对您的具体情况发表评论。

\n

但根本问题是您需要同步您的startcancel. 因此,如果您requester尚未执行此操作,则应将其包装在提供线程安全交互的对象中。在 Swift 并发中执行此操作的标准方法是使用参与者。

\n
\n

例如,让我们假设您正在包装一个网络请求。要与此同步您的访问,您可以创建一个参与者:

\n
actor ResponseDataRequest {\n    private var handle: Handle?\n\n    func start(completion: @Sendable @escaping (Data?, Error?) -> Void) {\n        // start it and save handle for cancelation, e.g.,\n        \n        handle = requestor.start(...)\n    }\n\n    func cancel() {\n        handle?.cancel()\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

这将网络请求的启动和取消包装在参与者中。然后你可以做这样的事情:

\n
func doSomething() async throws -> ResultData {\n    let responseDataRequest = ResponseDataRequest()\n\n    return try await withTaskCancellationHandler {\n        Task { await responseDataRequest.cancel() }\n    } operation: {\n        return try await withCheckedThrowingContinuation { continuation in\n            Task {\n                await responseDataRequest.start { result, error in\n                    if let error = error {\n                        continuation.resume(throwing: error)\n                    } else {\n                        let resultData = ResultData(result)\n                        continuation.resume(returning: resultData)\n                    }\n                }\n            }\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

当您验证所有内容都与您检查的延续一起工作时,您显然可以转向不安全的延续。

\n