ios - gcd - grand central dispatch swift
Colas concurrentes vs serie en GCD (3)
Estoy luchando por comprender completamente las colas concurrentes y en serie en GCD. Tengo algunos problemas y espero que alguien me pueda responder claramente y en el momento.
Estoy leyendo que las colas en serie se crean y se usan para ejecutar las tareas una después de la otra. Sin embargo, ¿qué ocurre si:
- Creo una cola en serie
- Utilizo
dispatch_async
(en la cola serie que acabo de crear) tres veces para despachar tres bloques A, B, C
¿Se ejecutarán los tres bloques?
en orden A, B, C porque la cola es serial
O
- concurrentemente (al mismo tiempo en hilos parralel) porque utilicé el envío ASYNC
Estoy leyendo que puedo usar
dispatch_sync
en colas concurrentes para ejecutar bloques uno después del otro. En ese caso, ¿POR QUÉ existen colas en serie, ya que siempre puedo usar una cola concurrente donde puedo enviar SYNCHRONOUSLY todos los bloques que quiera?Gracias por cualquier buena explicación!
Aquí hay un par de experimentos que he hecho para hacerme entender sobre estas colas concurrent
y en serial
con Grand Central Dispatch
.
func doLongAsyncTaskInSerialQueue() {
let serialQueue = DispatchQueue(label: "com.queue.Serial")
for i in 1...5 {
serialQueue.async {
if Thread.isMainThread{
print("task running in main thread")
}else{
print("task running in background thread")
}
let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
let _ = try! Data(contentsOf: imgURL)
print("/(i) completed downloading")
}
}
}
La tarea se ejecutará en un hilo diferente (que no sea el hilo principal) cuando use async en GCD. Async significa ejecutar la siguiente línea, no espere hasta que se ejecute el bloque, lo que no bloquea el hilo principal y la cola principal. Desde su cola en serie, todos se ejecutan en el orden en que se agregan a la cola serie. Las tareas ejecutadas en serie siempre se ejecutan de a una por el único hilo asociado con la cola.
func doLongSyncTaskInSerialQueue() {
let serialQueue = DispatchQueue(label: "com.queue.Serial")
for i in 1...5 {
serialQueue.sync {
if Thread.isMainThread{
print("task running in main thread")
}else{
print("task running in background thread")
}
let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
let _ = try! Data(contentsOf: imgURL)
print("/(i) completed downloading")
}
}
}
La tarea puede ejecutarse en el hilo principal cuando usa sincronización en GCD. Sync ejecuta un bloque en una cola determinada y espera que se complete, lo que resulta en el bloqueo del hilo principal o la cola principal. Dado que la cola principal debe esperar hasta que el bloque enviado finalice, el hilo principal estará disponible para procesar bloques de colas que no sean cola principal. Por lo tanto, existe la posibilidad de que el código que se ejecuta en la cola de fondo se esté ejecutando en el hilo principal. Desde su cola serie, todos se ejecutan en el orden en que se agregan (FIFO).
func doLongASyncTaskInConcurrentQueue() {
let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent)
for i in 1...5 {
concurrentQueue.async {
if Thread.isMainThread{
print("task running in main thread")
}else{
print("task running in background thread")
}
let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
let _ = try! Data(contentsOf: imgURL)
print("/(i) completed downloading")
}
print("/(i) executing")
}
}
La tarea se ejecutará en el hilo de fondo cuando use async en GCD. Async significa ejecutar la siguiente línea, no espere hasta que se ejecute el bloque, lo que resulta en un bloqueo del hilo principal. Recuerde que en la cola concurrente, las tareas se procesan en el orden en que se agregan a la cola, pero con diferentes subprocesos conectados a la cola. Recuerde que no se supone que terminen la tarea en el orden en que se agregan a la cola. El orden de la tarea varía cada vez que los hilos se crean como necesariamente automáticamente. Las tareas se ejecutan en paralelo. Con más de eso (maxConcurrentOperationCount) se alcanza, algunas tareas se comportarán como una serie hasta que un hilo sea libre.
func doLongSyncTaskInConcurrentQueue() {
let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent)
for i in 1...5 {
concurrentQueue.sync {
if Thread.isMainThread{
print("task running in main thread")
}else{
print("task running in background thread")
}
let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
let _ = try! Data(contentsOf: imgURL)
print("/(i) completed downloading")
}
print("/(i) executed")
}
}
La tarea puede ejecutarse en el hilo principal cuando usa sincronización en GCD. Sync ejecuta un bloque en una cola determinada y espera que se complete, lo que resulta en el bloqueo del hilo principal o la cola principal. Dado que la cola principal debe esperar hasta que el bloque enviado finalice, el hilo principal estará disponible para procesar bloques de colas que no sean cola principal. Por lo tanto, existe la posibilidad de que el código que se está ejecutando en la cola de fondo realmente se esté ejecutando en el hilo principal. Debido a que su cola es concurrente, las tareas pueden no finalizar en el orden en que se agregan a la cola. Pero con la operación sincrónica sí lo hace, aunque pueden ser procesados por diferentes hilos. Entonces, se comporta como esta es la cola serial.
Aquí hay un resumen de estos experimentos
Recuerde usar GCD, solo está agregando tareas a la cola y realizando tareas desde esa cola. Cola envía su tarea en el hilo principal o en el fondo dependiendo de si la operación es síncrona o asíncrona. Los tipos de colas son en serie, concurrentes, cola de despacho principal. Toda la tarea que realiza se realiza de forma predeterminada desde la cola de despacho principal. Ya hay cuatro colas simultáneas globales predefinidas para su aplicación y una cola principal (DispatchQueue.main). Usted también puede crear manualmente su propia cola y realizar tareas desde esa cola.
La tarea relacionada con la interfaz de usuario se debe realizar siempre desde el hilo principal enviando la tarea a la cola principal. La utilidad de la mano corta es DispatchQueue.main.sync/async
mientras que las operaciones relacionadas / pesadas de red siempre se deben realizar de forma asincrónica, independientemente del hilo que utilice. o fondo
EDITAR: Sin embargo, hay casos en los que necesita realizar operaciones de llamadas de red de forma síncrona en un hilo de fondo sin congelar la IU (por ejemplo, restaurar el Token OAuth y esperar si tiene éxito o no). Debe ajustar ese método dentro de una operación asincrónica. las operaciones se ejecutan en el orden y sin bloquear el hilo principal.
func doMultipleSyncTaskWithinAsynchronousOperation() {
let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent)
concurrentQueue.async {
let concurrentQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.default)
for i in 1...5 {
concurrentQueue.sync {
let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
let _ = try! Data(contentsOf: imgURL)
print("/(i) completed downloading")
}
print("/(i) executed")
}
}
}
EDITAR EDITAR: Puedes ver un video de demostración here
Si entiendo correctamente cómo funciona GCD, creo que hay dos tipos de DispatchQueue
, serial
y concurrent
, al mismo tiempo, hay dos formas en que DispatchQueue
despacha sus tareas, el closure
asignado, el primero es async
y el otro es sync
Aquellos juntos determinan cómo se ejecuta realmente el cierre (tarea).
Encontré que serial
y concurrent
significan cuántos hilos puede usar esa cola, serial
significa uno, mientras que concurrent
significa muchos. Y sync
y async
significan que la tarea se ejecutará en qué subproceso, el subproceso de la persona que llama o el subproceso subyacente a esa cola, la sync
significa ejecutar en el subproceso mientras que async
significa ejecutar en el subproceso subyacente.
El siguiente es un código experimental que se puede ejecutar en Xcode playground.
PlaygroundPage.current.needsIndefiniteExecution = true
let cq = DispatchQueue(label: "concurrent.queue", attributes: .concurrent)
let cq2 = DispatchQueue(label: "concurent.queue2", attributes: .concurrent)
let sq = DispatchQueue(label: "serial.queue")
func codeFragment() {
print("code Fragment begin")
print("Task Thread:/(Thread.current.description)")
let imgURL = URL(string: "http://.com/questions/24058336/how-do-i-run-asynchronous-callbacks-in-playground")!
let _ = try! Data(contentsOf: imgURL)
print("code Fragment completed")
}
func serialQueueSync() { sq.sync { codeFragment() } }
func serialQueueAsync() { sq.async { codeFragment() } }
func concurrentQueueSync() { cq2.sync { codeFragment() } }
func concurrentQueueAsync() { cq2.async { codeFragment() } }
func tasksExecution() {
(1...5).forEach { (_) in
/// Using an concurrent queue to simulate concurent task executions.
cq.async {
print("Caller Thread:/(Thread.current.description)")
/// Serial Queue Async, tasks run serially, because only one thread that can be used by serial queue, the underlying thread of serial queue.
//serialQueueAsync()
/// Serial Queue Sync, tasks run serially, because only one thread that can be used by serial queue,one by one of the callers'' threads.
//serialQueueSync()
/// Concurrent Queue Async, tasks run concurrently, because tasks can run on different underlying threads
//concurrentQueueAsync()
/// Concurrent Queue Sync, tasks run concurrently, because tasks can run on different callers'' thread
//concurrentQueueSync()
}
}
}
tasksExecution()
Espero que pueda ser útil.
Un ejemplo simple: tienes un bloque que tarda un minuto en ejecutarse. Usted lo agrega a una cola del hilo principal. Veamos los cuatro casos.
- async - concurrente: el código se ejecuta en un hilo de fondo. El control vuelve inmediatamente al hilo principal (y a la IU). El bloque no puede suponer que es el único bloque que se ejecuta en esa cola
- async - serial: el código se ejecuta en una cadena de fondo. El control vuelve inmediatamente al hilo principal. El bloque puede asumir que es el único bloque que se ejecuta en esa cola
- sincronización: concurrente: el código se ejecuta en un subproceso en segundo plano, pero el subproceso principal espera a que finalice, bloqueando las actualizaciones de la interfaz de usuario. El bloque no puede suponer que es el único bloque que se ejecuta en esa cola (podría haber agregado otro bloque usando async unos segundos antes)
- sync - serial: el código se ejecuta en un subproceso en segundo plano, pero el subproceso principal espera a que termine, bloqueando las actualizaciones de la interfaz de usuario. El bloque puede asumir que es el único bloque que se ejecuta en esa cola
Obviamente, no usaría ninguno de los dos últimos para procesos de larga ejecución. Normalmente lo ve cuando intenta actualizar la interfaz de usuario (siempre en el hilo principal) de algo que puede estar ejecutándose en otro hilo.