Acabo de empezar a aprender sobre GCD y tengo problemas porque mi código todavía se ejecuta en el hilo principal mientras creaba una cola en segundo plano. Este es mi código:
import UIKit class ViewController: UIViewController { let queue = DispatchQueue(label: "internalqueue", qos: .background) override func viewDidLoad() { super.viewDidLoad() dispatchFun { assert(Thread.isMainThread) let x = UIView() } } func dispatchFun(handler: @escaping (() -> ())) { queue.sync { handler() } } }
Lo suficientemente sorprendente (para mí), es que este código no arroja ningún error. Esperaría que la afirmación fallara. Espero que el código no se ejecute en el hilo principal. En el depurador veo que al construir la instancia x
, estoy en mi cola en el subproceso 1 (al ver la etiqueta). Extraño, porque normalmente veo la etiqueta del subproceso principal en el subproceso 1. ¿Mi cola está programada en el subproceso principal (subproceso 1)?
Cuando cambio sync
por async
, la aserción falla . Esto es lo que esperaría que sucediera también con la sync
. A continuación se muestra una imagen adjunta de los hilos cuando falló la afirmación. Esperaría ver exactamente la misma información de depuración cuando uso sync
en lugar de async
.
Al leer la descripción de sync
en la fuente de Swift, leí lo siguiente:
/// As an optimization, `sync(execute:)` invokes the work item on the thread which /// submitted it, except when the queue is the main queue or /// a queue targetting it.
Nuevamente: excepto cuando la cola es la cola principal
¿Por qué el método de sync
en una cola de envío en segundo plano hace que el código se ejecute en el subproceso principal, pero la async
no? Puedo leer claramente que el método de sincronización en una cola no debe ejecutarse en el hilo principal, pero ¿por qué mi código ignora ese escenario?
Creo que estás leyendo mal ese comentario en el encabezado. No se trata de si está despachando desde la cola main
, sino de si está despachando a la cola main
.
Entonces, aquí está la conocida optimización de sync
donde el bloque enviado se ejecutará en el hilo actual:
let backgroundQueue = DispatchQueue(label: "internalqueue", attributes: .concurrent) // We'll dispatch from main thread _to_ background queue func dispatchingToBackgroundQueue() { backgroundQueue.sync { print(#function, "this sync will run on the current thread, namely the main thread; isMainThread =", Thread.isMainThread) } backgroundQueue.async { print(#function, "but this async will run on the background queue's thread; isMainThread =", Thread.isMainThread) } }
Cuando usas sync
, le estás diciendo a GCD "oye, haz que este hilo espere hasta que el otro hilo ejecute este bloque de código". Entonces, GCD es lo suficientemente inteligente como para darse cuenta "bueno, si este hilo no va a hacer nada mientras espero que se ejecute el bloque de código, también podría ejecutarlo aquí si puedo, y ahorrar el contexto costoso cambiar a otro hilo.”
Pero en el siguiente escenario, estamos haciendo algo en una cola en segundo plano y queremos enviarlo de vuelta a la cola main
. En este caso, GCD no realizará la optimización antes mencionada, sino que siempre ejecutará la tarea enviada a la cola main
en la cola main
:
// but this time, we'll dispatch from background queue _to_ the main queue func dispatchingToTheMainQueue() { backgroundQueue.async { DispatchQueue.main.sync { print(#function, "even though it's sync, this will still run on the main thread; isMainThread =", Thread.isMainThread) } DispatchQueue.main.async { print(#function, "needless to say, this async will run on the main thread; isMainThread =", Thread.isMainThread) } } }
Hace esto porque hay ciertas cosas que deben ejecutarse en la cola principal (como las actualizaciones de la interfaz de usuario), y si lo está enviando a la cola principal, siempre cumplirá con esa solicitud y no intentará hacer ninguna optimización para evitar cambios de contexto.
Consideremos un ejemplo más práctico del último escenario.
func performRequest(_ url: URL) { URLSession.shared.dataTask(with: url) { data, _, _ in DispatchQueue.main.sync { // we're guaranteed that this actually will run on the main thread // even though we used `sync` } } }
Ahora, generalmente async
cuando enviamos de regreso a la cola principal, pero el comentario en la documentación del encabezado de sync
solo nos informa que esta tarea enviada de regreso a la cola principal usando sync
en realidad se ejecutará en la cola principal, no en cola de fondo de URLSession
como de lo contrario podría temer.
Consideremos:
/// As an optimization, `sync(execute:)` invokes the work item on the thread which /// submitted it, except when the queue is the main queue or /// a queue targetting it.
Estás invocando sync()
en tu propia cola. ¿Esa cola es la cola principal o está dirigida a la cola principal? No, no es. Entonces, la excepción no es relevante y solo esta parte lo es:
sync(execute:)
invoca el elemento de trabajo en el hilo que lo envió
Por lo tanto, el hecho de que su queue
sea una cola en segundo plano no importa. El bloque es ejecutado por el subproceso donde se llamó a sync()
, que es el subproceso principal (que llamó viewDidLoad()
, que llamó dispatchFun()
).