This is something that was asked already for a Combine Publisher type here. But is such a common pattern that I'm still curious what's the best approach.
The question is, when you have a type that takes care of cancelling, but that must be created inside the async operation closure, how you keep it around safely?
public func asyncFunc() async throws -> FeatureQueryResult {
var cancellation: TypeThatCancels? // Publisher subscription, network operation...
return try await withTaskCancellationHandler {
cancellation?.cancel()
} operation: {
try await withCheckedThrowingContinuation { continuation in
cancellation = StartTheWork() {
continuation.resume(with: $0)
}
}
}
}
The compiler will complain that we're referencing var cancellation
from concurrent code, which is of course true.
Looking online the only solutions I've seen is to wrap the cancellation
in a class, but is that the correct and intended solution? It feels too fragile. Seems like it just shuts up the compiler but probably doesn't fix the real issues.
Another approach would be to wrap it in an actor but doesn't that feel too much? Also at that point you need to care a lot of timings and check for cancellation inside the operation closure too.
I'm surprised to not find any more official or well explained solutions for such a common scenario. Any advice will be appreciated. Thanks!
Sometimes the workaround is easy, if the object we're using can give us a cancellation token separetly from the closure that gives us the results then we can just move the assignment outside.
public func asyncFunc() async throws -> FeatureQueryResult {
var request = someObject.createRequest() // gives us something that can start and also cancel the request
return try await withTaskCancellationHandler {
request.cancel()
} operation: {
try await withCheckedThrowingContinuation { continuation in
request.start {
continuation.resume(with: $0)
}
}
}
}
But that is rarely the case.