Preguntas
¿En qué situaciones el executor por defecto de Swift resulta insuficiente y cuándo debería considerarse un ejecutor personalizado?
- No se quiere usar el pool de hilos global cooperativo.
- Se quiere usar una cola serial específica.
- Se quiere enganchar un hilo específico.
Puede ser el caso de una biblioteca de terceros que espera recibir un DispatchQueue serial para orquestar sus operaciones.
¿Qué diferencia hay entre un SerialExecutor y un TaskExecutor, y para qué sirve cada uno?
Un SerialExecutor sirve para despachar tareas en un actor, mientras que un TaskExecutor sirve para despachar tareas en un Task.
¿Por qué es importante que el actor mantenga una referencia fuerte al ejecutor cuando se usa asUnownedSerialExecutor()?
asUnownedSerialExecutor() entrega una referencia simple a un objeto en el heap sin conteo de referencias. Por esta razón, si no se retiene la referencia, el objeto se pierde.
¿Cómo funciona el método enqueue(_:) dentro de un SerialExecutor basado en DispatchQueue?
Se despacha una tarea en la cola seria (i.e. dispatchQueue.async) que consiste en ejecutar síncronamente un UnownedJob en un SerialExecutor (i.e. unownedJob.runSynchronously(on: unownedExecutor)).
final class DispatchQueueExecutor: SerialExecutor {
private let dispatchQueue: DispatchQueue
init(dispatchQueue: DispatchQueue) {
self.dispatchQueue = dispatchQueue
}
func enqueue(_ job: consuming ExecutorJob) {
let unownedJob = UnownedJob(job)
let unownedExecutor = asUnownedSerialExecutor()
dispatchQueue.async {
unownedJob.runSynchronously(on: unownedExecutor)
}
}
}
actor LoggingActor {
private let executor: DispatchQueueExecutor
nonisolated var unownedExecutor: UnownedSerialExecutor {
executor.asUnownedSerialExecutor()
}
init(dispatchQueue: DispatchQueue) {
self.executor = DispatchQueueExecutor(dispatchQueue: dispatchQueue)
}
func log(_ message: String) {
print("[\(Thread.current)]: \(message)")
}
}
¿Qué métodos permiten configurar una preferencia de ejecutor para tareas no aisladas a un actor?
Primero hay que crear un TaskExecutor. Notar en el siguiente código cómo se conforma TaskExecutor y cómo se despacha dentro de la cola serial (i.e. dispatchQueue.async) una tarea que consiste en ejecutar síncronamente el job recibido (i.e. unownedJob.runSynchronously(on: unownedExecutor)):
final class DispatchQueueTaskExecutor: TaskExecutor {
private let dispatchQueue: DispatchQueue
init(dispatchQueue: DispatchQueue) {
self.dispatchQueue = dispatchQueue
}
func enqueue(_ job: consuming ExecutorJob) {
let unownedJob = UnownedJob(job)
let unownedExecutor = asUnownedTaskExecutor()
dispatchQueue.async {
unownedJob.runSynchronously(on: unownedExecutor)
}
}
}
Luego, al definir el Task, se le pasa el ejecutor preferido:
struct DispatchQueueTaskExecutorTests {
@Test
func execute() async {
let queue = DispatchQueue(label: "com.logger.queue")
let taskExecutor = DispatchQueueTaskExecutor(dispatchQueue: queue)
await Task(executorPreference: taskExecutor) {
print("Task Executor example")
}.value
#expect(1 == 1)
}
}
¿Qué métodos permiten configurar una preferencia de ejecutor para tareas aisladas a un actor?
Para ejecutar una tarea aislada a un actor se usa runSynchronously(isolatedTo:taskExecutor:) en lugar de runSynchronously(on:) como se muestra a continuación:
final class DispatchQueueTaskExecutor: TaskExecutor {
private let dispatchQueue: DispatchQueue
init(dispatchQueue: DispatchQueue) {
self.dispatchQueue = dispatchQueue
}
func enqueue(_ job: consuming ExecutorJob) {
let unownedJob = UnownedJob(job)
let unownedExecutor = asUnownedTaskExecutor()
dispatchQueue.async {
unownedJob.runSynchronously(isolatedTo: DispatchQueueExecutor.loggingExecutor.asUnownedSerialExecutor(), taskExecutor: self.asUnownedTaskExecutor())
}
}
}
Mostrar un ejemplo donde se comparta un executor entre actores
Se debe mantener una referencia al executor como si fuera un singleton (e.g. static let loggingExecutor). Luego, el actor hará referencia a ese singleton desde var unownedExecutor: UnownedSerialExecutor.
extension DispatchQueueExecutor {
static let loggingExecutor = DispatchQueueExecutor(
dispatchQueue: DispatchQueue(label: "com.logger.queue", qos: .utility)
)
}
actor SharedExecutorLoggingActor {
nonisolated var unownedExecutor: UnownedSerialExecutor {
DispatchQueueExecutor.loggingExecutor.asUnownedSerialExecutor()
}
func log(_ message: String) {
print("[\(Thread.current)] \(message)")
}
}
Top comments (0)