2010-08-05 6 views
6

Actualmente estoy trabajando en una aplicación de iPhone y tengo una biblioteca de un tercero que tiene un comportamiento asíncrono, pero me gustaría envolverla con mi propia clase y hacer que parezca sincrónica.¿Cómo ajustar una clase asíncrona para que sea sincrónica? Use NSRunLoop?

La clase central en esta biblioteca, llamémosla clase de conexión, tiene varias funciones que tienen su resultado final resuelto cuando se llaman los métodos en una instancia de una clase de delegado. Lo que trato de hacer es ajustar esta clase y delegar para que parezca ser sincrónica en lugar de asincrónica. Si estuviera haciendo esto en Java, usaría FutureTask o CountdownLatch o simplemente join(). Pero no estoy seguro de la mejor manera de hacerlo en Objective C.

Comencé creando una extensión NSThread, NFCThread, que se ajusta al protocolo delegado mencionado anteriormente. La idea es que inicie y NFCThread, pase la instancia de NFCThread al método setDelegate de Connection, inicie el hilo y luego llame a un método asincrónico en Connection. Mi expectativa es que uno de los tres métodos de delegado en la instancia de NFCThread se llamaría finalmente haciendo que el hilo salga.

Para simular una unión, hice lo siguiente. He añadido una NSConditionalLock a NFCThread:

joinLock = [[NSConditionLock alloc] initWithCondition:NO]; 

El código alrededor de la llamada a la conexión se ve algo como esto:

NFCThread *t = [[NFCThread alloc] init]; 
[connection setDelegate:t]; 
[t start]; 

[connection openSession]; 
// Process errors, etc... 

[t.joinLock lockWhenCondition:YES]; 
[t.joinLock unlock]; 
[t release]; 
[connection setDelegate:nil]; 

El protocolo para el delegado tiene tres métodos. En NFCThread he implementado cada método algo como esto:

- (void)didReceiveMessage:(CommandType)cmdType 
        data:(NSString *)responseData 
       length:(NSInteger)length { 
    NSLog(@"didReceiveMessage"); 
    // Do something with data and cmdType... 
    [joinLock lock]; 
    [joinLock unlockWithCondition:YES]; 
    callBackInvoked = YES; 
} 

sobrecargué principal método de NFCThread de modo que simplemente bucles continuamente. Algo como esto:

while (!callBackInvoked) { ; } 

He descubierto que esto no es realmente una buena idea, ya que provocan uso de la CPU para ir a través del techo. Así que en lugar He intentado utilizar un bucle de ejecución de algunos ejemplos que encontré en este sitio:

NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; 
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; 

while (!callBackInvoked) { 
    [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; 
} 

En mis dos implementaciones, el hilo principal siempre está bloqueada y parece que ninguno de los métodos de delegado se llamó nunca. Sin embargo, sé que la biblioteca funciona correctamente y que las llamadas a los métodos de delegado normalmente se llaman.

Siento que me falta algo obvio aquí. Cualquier ayuda muy apreciada.

Rich

Respuesta

0

Ok, entonces hay algunos problemas diferentes aquí, estoy tratando de pensar por dónde empezar.

Pero para que comprendamos lo que está intentando lograr, cuando dice que quiere que la llamada "aparezca" sincrónica, ¿quiere decir que quiere que la llamada se bloquee? ¿Estás haciendo esta llamada desde el hilo principal?Si es así, parece que estás bloqueando el hilo principal por diseño.

Tenga en cuenta que la biblioteca de terceros probablemente esté programando eventos en el ciclo de ejecución principal. Puede crear su propio bucle de ejecución y ejecutarlo en otro subproceso, pero ¿le ha dicho a la otra biblioteca que use ese bucle de ejecución para sus eventos? (Por ejemplo, podría estar haciendo solicitudes de red asíncronas que están programadas en el ciclo de ejecución principal que ha bloqueado)

Me replantearía lo que hace un poco, pero primero tendríamos que saber si su intención es para bloquear el hilo desde el que realiza esta llamada. Además, ¿necesitas soportar iPhoneOS 3.x?

+0

Gracias, Firoze. Sí, nuestra intención es tener nuestra llamada síncrona para bloquear. Esta biblioteca de terceros realmente está interactuando con una tarjeta SD, pero creo que es solo un detalle. Además, luego de una inspección más cercana, solo determiné que las llamadas a los métodos de delegado también ocurren en el hilo principal. ¿Podría esto hacer que toda esta discusión sea discutible? – richever

+1

Bueno, no estoy seguro de que sea discutible. El problema es que si desea bloquear la llamada y bloquea el hilo principal, esta otra biblioteca no puede finalizar su trabajo. Entonces, una respuesta sería hacer la llamada síncrona desde un hilo diferente, que bloquearía * ese * hilo, pero aún permitiría que el hilo principal continuase funcionando normalmente. Por supuesto, si termina creando y bloqueando muchos hilos de esta manera, eso no es muy bueno. En cualquier caso, pensaría en * por qué * quieres esta sincronización y quizás pienses en una forma no bloqueante para hacer lo mismo (requiere saber más de lo que estás tratando de hacer aquí) –

+0

Llamo al método de bloqueo en un hilo diferente ahora, como sugirió Firoze, y parece que funciona. Más o menos Ver mi pregunta de seguimiento: http://stackoverflow.com/questions/3444557/error-at-nsrunloop-después-return-from-thread-method-with-nsautoreleasepool – richever

4

¿Quieres un semáforo, lo que le permitirá a su código base primaria para bloquear hasta que su devolución de llamada asincrónica señala el semáforo y permite que continúe.

Los semáforos están disponibles en iOS 4 a través de Grand Central Dispatch.

Parece que el comportamiento de los semáforos se puede implementar en iOS 3 con NSCondition.

+0

Gracias Seamus, pero desafortunadamente la biblioteca que menciono en esta pregunta se compila en 3.x entonces, afaik, no podemos usar GCD. ¿Cualquier otra sugerencia? – richever

2

Acabo de implementar algo similar. El contexto fue: - ajustar varias llamadas a ZipArchive en una cadena de fondo - para cada llamada para descomprimir, mostrar un contador de progreso diferente (girando infinitamente la rueda con el nombre del archivo que se está expandiendo) - realizar una tarea de limpieza cuando todo archivos se han ampliado

Resulta que NSConditionLock hace que sea sencillo, y el código funciona de la siguiente manera:

NSConditionLock* lock = alloc/init 
for(int idx = 0; idx < [list count]; idx++) { 
    NSString* fileName = ["getIdx's element in list"] 
    [cond lockWhenCondition:idx]; // 

    ... prepare things before forking 

    [cond unlockWithCondition:-1]; 
    [Notification forkBlock:^(void){ 
    [cond lockWhenCondition:-1]; 

    NSAutoreleasePool *pool = [alloc/init]; 
    [ZipArchive unzipFile:fileName]; 
    [pool release]; 
    [cond unlockWithCondition:idx+1]; 
    }]; 
} 

[cond lockWhenCondition:[list count]; 
// all the tasks are now completed 

Cada uno de los bloques se han programado en un subproceso en segundo plano. La clase de notificación se encarga de animar el UIView con la rueca y envolver el bloque en otro hilo. El bloqueo/desbloqueo debe ser llamado desde el mismo hilo, por lo que la condición es lo que habilita el ping-pong entre los hilos de fondo y de fondo (-1 para el fondo, 1,2,3..n para el primer plano).

0

Lo que quiere hacer es usar NSCondition para esperar en el hilo actual, y señal en el segundo hilo para permitir que el primero continúe.

- (void) firstThread 
{ 
    workIsDone = NO; 
    //Start second thread here 
    [condition lock]; 
    while (!workIsDone) { 
     [condition wait]; 
    } 
    [condition unlock]; 

    // Keep going 
} 

- (void) secondThread 
{ 
    [condition lock]; 

    //Do some work. 
    workIsDone = YES; 
    [condition signal]; 
    [condition unlock]; 
} 
Cuestiones relacionadas