Estoy probando un código que realiza un procesamiento asincrónico utilizando Grand Central Dispatch. El código de prueba se ve así:
[object runSomeLongOperationAndDo:^{
STAssert…
}];
Las pruebas tienen que esperar a que termine la operación. Mi solución actual se ve así:
__block BOOL finished = NO;
[object runSomeLongOperationAndDo:^{
STAssert…
finished = YES;
}];
while (!finished);
Lo que parece un poco crudo, ¿conoces una mejor manera? Podría exponer la cola y luego bloquear llamando dispatch_sync
:
[object runSomeLongOperationAndDo:^{
STAssert…
}];
dispatch_sync(object.queue, ^{});
... pero eso es tal vez exponer demasiado en el object
.
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
conwhile (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW)) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]]; }
Además de la técnica de semáforo cubierta exhaustivamente en otras respuestas, ahora podemos usar XCTest en Xcode 6 para realizar pruebas asincrónicas a través de
XCTestExpectation
. Esto elimina la necesidad de semáforos al probar el código asincrónico. Por ejemplo:Por el bien de los futuros lectores, si bien la técnica de despacho de semáforos es una técnica maravillosa cuando es absolutamente necesaria, debo confesar que veo demasiados desarrolladores nuevos, no familiarizados con buenos patrones de programación asincrónica, gravitan demasiado rápido a los semáforos como un mecanismo general para hacer asíncrono Las rutinas se comportan sincrónicamente. Peor aún, he visto a muchos de ellos usar esta técnica de semáforo desde la cola principal (y nunca debemos bloquear la cola principal en las aplicaciones de producción).
Sé que este no es el caso aquí (cuando se publicó esta pregunta, no había una buena herramienta como
XCTestExpectation
; también, en estas suites de prueba, debemos asegurarnos de que la prueba no termine hasta que se realice la llamada asincrónica). Esta es una de esas situaciones raras en las que podría ser necesaria la técnica de semáforo para bloquear el hilo principal.Entonces, con mis disculpas al autor de esta pregunta original, para quien la técnica del semáforo es sólida, escribo esta advertencia a todos los nuevos desarrolladores que ven esta técnica del semáforo y consideran aplicarla en su código como un enfoque general para tratar con asincrónico métodos: Tenga en cuenta que nueve de cada diez veces, la técnica del semáforo no esEl mejor enfoque al encontrar operaciones asincrónicas. En su lugar, familiarícese con los patrones de cierre / bloqueo de finalización, así como con los patrones y notificaciones de protocolo delegado. A menudo, estas son formas mucho mejores de tratar con tareas asincrónicas, en lugar de utilizar semáforos para que se comporten de forma sincrónica. Por lo general, hay buenas razones por las que las tareas asincrónicas se diseñaron para que se comporten de forma asincrónica, por lo que debe utilizar el patrón asincrónico correcto en lugar de intentar que se comporten de forma sincrónica.
fuente
NSOperationQueue
. A menos que use algo así como un semáforo, las descargas de documentosNSOperation
aparecerán de inmediato y no habrá una cola real de descargas, sino que procederán simultáneamente, lo que no quiero. ¿Son razonables los semáforos aquí? ¿O hay una mejor manera de hacer que NSOperations espere el final asincrónico de los demás? ¿O algo mas?AFHTTPRequestOperation
objetos, debe crear una operación de finalización (que dependerá de las otras operaciones). O use grupos de despacho. Por cierto, dices que no quieres que se ejecuten simultáneamente, lo cual está bien si eso es lo que necesitas, pero pagas una seria penalización de rendimiento haciendo esto secuencialmente en lugar de concurrentemente. Generalmente usomaxConcurrentOperationCount
de 4 o 5.Recientemente volví a este problema y escribí la siguiente categoría en
NSObject
:De esta manera, puedo convertir fácilmente una llamada asincrónica con una devolución de llamada en una sincrónica en las pruebas:
fuente
En general, no use ninguna de estas respuestas, a menudo no se escalan (hay excepciones aquí y allá, claro)
Estos enfoques son incompatibles con la forma en que GCD está destinado a funcionar y terminarán causando puntos muertos y / o agotando la batería mediante un sondeo sin parar.
En otras palabras, reorganice su código para que no haya una espera síncrona de un resultado, sino que trate con un resultado que se le notifique sobre un cambio de estado (por ejemplo, devoluciones de llamada / protocolos delegados, estar disponible, desaparecer, errores, etc.). (Estos se pueden refactorizar en bloques si no te gusta el infierno de devolución de llamada). Porque así es como exponer el comportamiento real al resto de la aplicación que esconderlo detrás de una fachada falsa.
En su lugar, use NSNotificationCenter , defina un protocolo de delegado personalizado con devoluciones de llamada para su clase. Y si no le gusta muckear con devoluciones de llamadas delegadas, envuélvalas en una clase proxy concreta que implemente el protocolo personalizado y guarde los diversos bloques en propiedades. Probablemente también proporcione constructores convenientes también.
El trabajo inicial es un poco más, pero reducirá la cantidad de horribles condiciones de carrera y encuestas de asesinatos de batería a largo plazo.
(No pidas un ejemplo, porque es trivial y también tuvimos que invertir el tiempo para aprender los conceptos básicos del objetivo c).
fuente
Aquí hay un ingenioso truco que no usa un semáforo:
Lo que debe hacer es esperar usando
dispatch_sync
un bloque vacío para esperar sincrónicamente en una cola de despacho en serie hasta que se complete el bloque A-Synchronous.fuente
Ejemplo de uso:
fuente
También hay SenTestingKitAsync que te permite escribir código como este:
(Consulte el artículo objc.io para obtener más detalles). Y desde Xcode 6 hay una
AsynchronousTesting
categoríaXCTest
que le permite escribir código como este:fuente
Aquí hay una alternativa de una de mis pruebas:
fuente
NSCondition
documentación de-waitUntilDate:
"Debe bloquear el receptor antes de llamar a este método". Entonces el-unlock
debería ser después-waitUntilDate:
.Esto lo hizo por mi.
fuente
A veces, los bucles de tiempo de espera también son útiles. ¿Puede esperar hasta obtener alguna señal (puede ser BOOL) del método de devolución de llamada asincrónica, pero qué pasa si no hay respuesta alguna y desea salir de ese bucle? Aquí debajo hay una solución, mayormente respondida anteriormente, pero con una adición de Timeout.
fuente
Solución muy primitiva al problema:
fuente
Swift 4:
Úselo en
synchronousRemoteObjectProxyWithErrorHandler
lugar deremoteObjectProxy
al crear el objeto remoto. No más necesidad de un semáforo.El siguiente ejemplo devolverá la versión recibida del proxy. Sin el
synchronousRemoteObjectProxyWithErrorHandler
se bloqueará (intentando acceder a memoria no accesible):fuente
Tengo que esperar hasta que se cargue un UIWebView antes de ejecutar mi método, pude hacer que esto funcione realizando comprobaciones de UIWebView ready en el hilo principal usando GCD en combinación con los métodos de semáforo mencionados en este hilo. El código final se ve así:
fuente