I'm working on a TaskStore
type that I'd love to be usable in any actor-isolated context (i.e., if someone wants to store view-layer tasks, they could isolate it to the MainActor
, or it could be used for database transactions). Here's a simplification of my current attempt at this:
final class BasicTaskStore<Key: Hashable & Sendable> {
private(set) var tasks: [Key: Task<Void, Never>] = [:]
private var taskIDs: [Key: UUID] = [:]
func addTask(
key: Key,
operation: @escaping @Sendable () async -> Void
) -> Task<Void, Never> {
let oldTask = tasks[key]
let id = UUID()
let newTask = Task {
await oldTask?.value
await operation()
if self.taskIDs[key] == id { // ERROR: Capture of 'self' with non-sendable type 'BasicTaskStore<Key>' in a `@Sendable` closure
tasks.removeValue(forKey: key)
}
}
tasks[key] = newTask
taskIDs[key] = id
return newTask
}
}
This almost works, but as we can see, accessing self
at the end of the Task causes a compiler error.
I would love if there was a solution where I could ensure at compile-time that self
would be isolated to the same context as when the method is called, but I'm not aware of any way of doing that (I tried various combinations of using #isolation
and @isolated(any)
).
One alternative would be using a lock, but it would be nice if I could avoid that, as it seems logically like it should be possible even if it's not with the current tools available in the language.
Any ideas?