TL;DR: Checking if it's ok to use @_inheritActorContext
& @_implicitSelfCapture
on a Task
factory method.
We have started using Swift Concurrency heavily in our SDK and when integrating it with application code, rightfully, a good number of Task
s are created to call SDK methods. The SDK surfaces errors as much as possible to allow consuming code to handle them as they see fit and provide as much information as possible to the caller.
To that end we've created some Task
factory methods that allow calling throwing functions and logs any errors thrown when used in a "terminal" context (e.g. in UI code). The factory methods simply create a Task
with a do
/catch
that calls the passed in closure and log any thrown errors.
To gain the advantages of creating a Task
"in context" we copied Task
s init
method and added attributes @_inheritActorContext
and @_implicitSelfCapture
to our passed in operation closure.
We found that without these (especially @_inheritActorContext
) that we are required to await
methods that should not require it; e.g. calling @MainActor
isolated functions from the @MainActor
context; which seemingly is exactly what this @_inheritActorContext
attribute is for.
I am just looking to make sure this is a valid thing to do in current and future Swift releases and learn of any pitfalls using them might create. They are currently working as expected but Concurrency is complicated and I am looking to be sure this is fundamentally correct code/usage.
Here is one of our implementation for examination...
@discardableResult
func spawn<TaskResult>(
name: String = "Task",
logger: Logger = taskLogger,
type: OSLogType = .error,
privacy: OSLogPrivacy = .private,
@_inheritActorContext @_implicitSelfCapture operation: @escaping @Sendable () async throws -> TaskResult
) -> Task<TaskResult?, Never> {
return Task {
do {
return try await operation()
}
catch {
logger.error("\(name, privacy: .public) failed: error=\(error, privacy: privacy)")
return nil
}
}
}