2011-12-20 13 views
9

Estoy escribiendo un módulo iOS que actualmente envía un correo electrónico de forma asincrónica (usando delegados). Utiliza SKPSMTPMessage que funciona muy bien. Mi problema es que el cliente quiere que el código bloquee completamente el hilo hasta después de que se haya enviado (o no se haya enviado). Por lo tanto, básicamente están solicitando una solución sincrónica, cuando actualmente intentará enviar el correo electrónico y luego regresar de ese bloque de código antes de que se haya enviado el correo electrónico.¿Envolviendo llamadas asincrónicas en un hilo de bloqueo síncrono?

Así que, en lugar de tratar de reescribir el código SKPSMTPMessage de forma síncrona (no parece que haya ninguna opción síncrona para él), espero encontrar la forma de ajustar ese bloque de código asíncrono en su propio hilo y tal vez hacer que el hilo principal espere hasta que finalice por completo (delegados y todo).

He probado unos cuantos métodos diferentes usando NSOperation s y NSThread pero tal vez no lo estoy haciendo algo bien, porque cada vez que intento de bloquear el hilo principal, el delegado asincrónica llama todavía nunca parecen terminar (hacer que vuelvan en el hilo principal o algo así?).

Cualquier información o incluso otras ideas apreciadas.

PD ~ Me doy cuenta de que esto es un poco al revés. En la mayoría de los casos, asíncrono parece ser el camino a seguir, pero este es un caso especial y el cliente tiene sus razones para quererlo.

EDIT: Gracias por toda la contribución. Tal y como sugiere una de las respuestas, Acabé usando un bucle while que esperó a que los delegados a regresar todavía permiten la Runloop continuar así de este modo:

while(![messageDelegate hasFinishedOrFailed]){ 
    // Allow the run loop to do some processing of the stream 
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; 
} 

Respuesta

4

No creo que haya ninguna manera de hacer exactamente esto sin modificar SKPSMTPMessage. La clase no está usando hilos por separado; en lugar se trata de utilizar un NSStreamin concert with the thread's run loop para evitar el bloqueo:

[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] 
          forMode:NSRunLoopCommonModes]; 
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] 
          forMode:NSRunLoopCommonModes]; 

La corriente está actuando como una fuente de entrada para la run loop; el ciclo de ejecución es libre de procesar otros eventos hasta que algo suceda con la secuencia, en cuyo momento la secuencia notifica a su delegado. Todo sigue sucediendo en el hilo original (principal); si intenta bloquearlo usted mismo, también bloqueará el ciclo de ejecución y no podrá hacer nada con la transmisión.

Otros ya han señalado que bloquear el hilo principal es una mala idea; aparte de los problemas de UX, el sistema puede terminar cualquier aplicación que no responda a eventos por un período demasiado largo. Dicho esto, puede poner la configuración del mensaje completo en segundo plano, dándole su propio ciclo de ejecución para que la transmisión funcione, y bloquee el hilo principal, usando GCD. Desafortunadamente, no puedo pensar en una forma para que el delegado señalice que está hecho sin sondeo.

dispatch_queue_t messageQueue; 
messageQueue = dispatch_queue_create("com.valheru.messageQueue", NULL); 

// dispatch_sync blocks the thread on which it's called 
dispatch_sync(messageQueue, ^{ 
    [messageManager tryToDeliverMessage]; 
}); 
dispatch_release(messageQueue); 

Dónde tryToDeliverMessage se ve algo como:

- (void) tryToDeliverMessage { 
    // Create the message and let it run... 

    // Poll a flag on the delegate 
    while(![messageDelegate hasFinishedOrFailed]){ 
     // Allow the run loop to do some processing of the stream 
     [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; 
    } 

    return; 
} 
+0

Sí, eso parece ser más y más lo que tengo que hacer y, sin embargo, el código se distribuye a través de varios métodos. Puedo decir que tratar de mover las cosas de manera que todo ocurra sincrónicamente va a ser un dolor. Parece que si de alguna manera tomo el trabajo runLoop fuera de la ecuación, eso ayudaría. Supongo que esperaba simplemente envolver el bloque de código en un hilo separado ... ¿entonces el lazo de ejecución no se ejecutaría en ese hilo y no en el hilo principal? No parece ser el caso, aunque – valheru

+0

Gracias, pero como publiqué como un comentario a otra posible respuesta, el módulo no debería tener ningún acceso a la interfaz de usuario en sí, ya que se incluirá en aplicaciones separadas. Se supone que es más un proceso en segundo plano en sí mismo que simplemente se inicia cuando la aplicación se inicia y, cuando termina, le da control total a la aplicación. – valheru

+0

Si crea un nuevo hilo, el ciclo de ejecución no se ejecuta automáticamente; tiene que iniciarse manualmente. Creo que poner la mensajería en segundo plano es la única posibilidad, sin embargo. Ver mi actualización para una posible forma de hacer eso. –

2

me corrija si estoy mal entendido las limitaciones, pero, ¿es el objetivo final evitar que el usuario navegue después de enviar un correo electrónico? Si es así, entonces, quizás agregar una especie de vista de superposición de progreso de pantalla completa como una subvista de la ventana sería una buena solución. Inhabilite la interacción del usuario y elimine la vista cuando obtiene una devolución de llamada correcta o fallida a través de los métodos de delegado.

+1

Gracias por la respuesta. En realidad, es un módulo que los desarrolladores utilizarán en su código, por lo que no debería tener acceso real a la interfaz de usuario ... solo tiene la intención de causar un pequeño retraso cuando la aplicación se inicia por primera vez. – valheru

+1

Esta es una solución buena y ampliamente utilizada. @valheru debería considerar NO bloquear el hilo principal bajo ninguna circunstancia, ya que podría haber un proceso en segundo plano que desea realizar cambios en el hilo principal (como una actualización de UI, fusión de datos Coredata, notificaciones, etc.). – Jeremy

+0

El OP no funciona con el control total de la aplicación, solo es un módulo relacionado con el envío de correo electrónico. Además, si el hombre que firma el cheque quiere que se bloquee, se bloqueará. :) –

22

me gustaría probar el uso de semáforos de despacho. Desde la página del manual de dispatch_semaphore_create(3):

dispatch_semaphore_t sema = dispatch_semaphore_create(0); 

dispatch_async(queue, ^{ 
    foo(); 
    dispatch_semaphore_signal(sema); 
}); 

bar(); 

dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); 
dispatch_release(sema); 
sema = NULL; 

La llamada a dispatch_semaphore_wait() bloqueará hasta que la llamada a dispatch_semaphore_signal() ultima.

+0

Gracias tiene sentido. Como usar mutexes. ¿Es compatible con esta forma a través de iOS? ¿Se puede llamar la señal desde un controlador de delegado? Y finalmente, ¿se encontrará con el mismo problema con el uso de runLoop (consulte la respuesta sobre el uso de SKpsMTPMessage de runLoops). Es decir, ¿la llamada wait() bloqueará el runLoop que está usando SKPSMTPMessage? – valheru

+0

Es compatible con iOS, y puede señalar el semáforo desde cualquier lugar (siempre que tenga una referencia), pero desafortunadamente parece que tendrá el mismo problema que otras soluciones. Como 'dispatch_semaphore_wait()' bloquea el hilo, puede que no sea la solución correcta. –

+1

No solo es compatible, esta es la solución correcta al problema de producir una versión de bloqueo de una secuencia de llamada asincrónica. Mucho mejor que hacer un faffing con bucles de ejecución, porque esta llamada síncrona se puede usar sin ningún problema dentro de otro código, por ejemplo dentro de un bloque en el fondo. – gnasher729

Cuestiones relacionadas