I'm highly suspecting that this would only be possible if there was a facility to (polymorphically) isolate the whole instance to a certain actor (pseudocode syntax):
final class BasicTaskStore<Key: Hashable & Sendable, #isolated A: Actor = #context>
because there's no way to ensure or express that it'll "stay" on the actor that addTask
inherits. To see this more clearly, imagine the existence of a second function
func alsoAddTask(
isolation: isolated any Actor,
key: Key,
operation: @escaping @Sendable () async -> Void
)
(or even, consider the original function just be called two times with two different actors passed to isolation
) — there's no way to guarantee that the actor will be the same, so the epilogue in Task
may race.
But this theoretical feature also introduces various problems, like tying the lifetime of the actor generic parameter to the lifetime of self
, which is incredibly annoying and complex, and only leaves actors with indefinite lifetime as an option, wich are your global actors.
TL;DR just use locks
EDIT: My bad, I have forgotten that a non-sendable type won't be callable from different actors anyway, even if it's in a @MainActor static let
. Indeed, similar to what's happening here, this has to do with newTask
not inheriting the isolation, so it would be otherwise possible to capture a non-sendable self
into non-isolated tasks.
The following compiles for me:
import Foundation
final class BasicTaskStore<Key: Hashable & Sendable> {
private(set) var tasks: [Key: Task<Void, Never>] = [:]
private var taskIDs: [Key: UUID] = [:]
func addTask(
key: Key,
isolation: isolated (any Actor)? = #isolation,
operation: @escaping @Sendable () async -> Void
) -> Task<Void, Never> {
let oldTask = tasks[key]
let id = UUID()
let newTask = Task {
let _ = isolation
await oldTask?.value
await operation()
if self.taskIDs[key] == id {
tasks.removeValue(forKey: key)
}
}
tasks[key] = newTask
taskIDs[key] = id
return newTask
}
}
— the isolation
actor just has to be captured into the task using e.g. let _ =
until the closure isolation control proposal is accepted.