2012-05-09 11 views
19

Me preguntaba por qué cuando creas un temporizador repetitivo en un bloque GCD no funciona.Ejecutar NSTimer repetitivo con GCD?

Esto funciona bien:

-(void)viewDidLoad{ 
    [super viewDidLoad]; 
    [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(runTimer) userInfo:nil repeats:YES]; 
} 
-(void)runTimer{ 
    NSLog(@"hi"); 
} 

Pero este trabajo doesent:

dispatch_queue_t myQueue; 

-(void)viewDidLoad{ 
    [super viewDidLoad]; 

    myQueue = dispatch_queue_create("someDescription", NULL); 
    dispatch_async(myQueue, ^{ 
     [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(runTimer) userInfo:nil repeats:YES]; 
    }); 
} 
-(void)runTimer{ 
    NSLog(@"hi"); 
} 

Respuesta

57

NSTimers están programados en el hilo de run loop actual. Sin embargo, los subprocesos de envío de GCD no tienen bucles ejecutados, por lo que la programación de temporizadores en un bloque de GCD no va a hacer nada.

Hay tres alternativas razonables:

  1. averiguar lo ejecutan bucle que desea programar el temporizador, y explícitamente lo hace. Use +[NSTimer timerWithTimeInterval:target:selector:userInfo:repeats:] para crear el temporizador y luego -[NSRunLoop addTimer:forMode:] para programarlo realmente en el ciclo de ejecución que desea usar. Esto requiere tener un control en el bucle de ejecución en cuestión, pero puede usar +[NSRunLoop mainRunLoop] si desea hacerlo en el hilo principal.
  2. Cambie al uso de un temporizador dispatch source. Esto implementa un temporizador en un mecanismo compatible con GCD, que ejecutará un bloque en el intervalo que desee en la cola de su elección.
  3. Explicitamente dispatch_async() volver a la cola principal antes de crear el temporizador. Esto es equivalente a la opción n. ° 1 utilizando el ciclo de ejecución principal (ya que también creará el temporizador en el hilo principal).

Por supuesto, la verdadera pregunta aquí es, ¿por qué está creando un temporizador de una cola de GCD para empezar?

+0

otra alternativa siguiente segunda respuesta de Kevin, que resuelve mis problemas mediante la sustitución de la NSTimer con MSWeakTimer (github.com/mindsnacks/MSWeakTimer), y acaba de pasar la dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) como el dispatchQueue. –

+0

Gran resumen. Súper gracias. Si entiendo correctamente: 1. ¿Estás diciendo que ningún hilo de envío de GCD tiene un ciclo de ejecución ** excepto ** para el hilo principal? 2. Si es verdadero, ¿qué hay de dispatch_after? ¿Cómo se procesaría sin un runloop? ¿O es que también se implementa en un mecanismo consciente de GCD? – Honey

+0

Los hilos de @Honey Dispatch no tienen bucles ejecutados. El hilo principal no es un hilo de envío (bueno, a menos que estés usando 'dispatch_main()', y si es así, tampoco tiene un runloop, pero presumiblemente no lo estás haciendo). El runloop del subproceso principal se integra con GCD para drenar explícitamente la cola principal como parte del funcionamiento normal del runloop. 'dispatch_after' es parte de GCD (de ahí la palabra" dispatch "en el nombre), no tiene nada que ver con runloops. –

-1

Esto es mala idea. Estaba a punto de eliminar esta respuesta, pero la dejé aquí para evitar que otros cometieran el mismo error que yo. Gracias #Kevin_Ballard por señalar esto.

Sólo agregaría una línea a su ejemplo y que trabajarías al igual que lo ha escrito:

[[NSRunLoop currentRunLoop] run]

por lo que se obtendría:

-(void)viewDidLoad{ 
     [super viewDidLoad]; 

     myQueue = dispatch_queue_create("someDescription", NULL); 
     dispatch_async(myQueue, ^{ 
      [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(runTimer) userInfo:nil repeats:YES]; 
      [[NSRunLoop currentRunLoop] run] 
     }); 
    } 

Desde la cola myQueue contiene un NSThread y contiene un NSRunLoop y dado que el código en el dispatch_async se ejecuta en el contexto de ese NSThread, currentRunLoop devolverá un ciclo de ejecución detenido asociado con el hilo de la cola.

+1

Esta es una mala idea. Ahora has cooptado un hilo de envío para utilizarlo como un runloop, y nunca volverás a devolver el control del hilo a GCD. Esto es básicamente como poner un bucle infinito en un bloque de despacho. Hacerse cargo de los hilos de distribución y no devolver nunca el control de ellos a GCD perjudicará el rendimiento de todo el sistema, y ​​si haces eso con suficientes hilos, puedes hacer que GCD deje de procesar bloques enviados a colas no globales por completo. –

+0

Tu código solo necesita una forma de salir de runloop. Mantenga una referencia a NSTimer e invalídelo en el momento apropiado. – seedante

0

NSTimer está programado para ejecutar el runloop. En el código de pregunta, runloop de hilo enviado por GCD no se está ejecutando. Debe iniciarlo manualmente y debe haber una forma de salir del ciclo de ejecución, por lo que debe mantener una referencia al NSTimer e invalidarlo en el momento apropiado.

NSTimer tiene una fuerte referencia al objetivo, por lo que el objetivo no puede tener una referencia fuerte al temporizador, y runloop tiene una fuerte referencia al temporizador.

weak var weakTimer: Timer? 
func configurateTimerInBackgroundThread(){ 
    DispatchQueue.global().async { 
     // Pause program execution in Xcode, you will find thread with this name 
     Thread.current.name = "BackgroundThreadWithTimer" 
     // This timer is scheduled to current run loop 
     self.weakTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(runTimer), userInfo: nil, repeats: true) 
     // Start current runloop manually, otherwise NSTimer won't fire. 
     RunLoop.current.run(mode: .defaultRunLoopMode, before: Date.distantFuture) 
    } 
} 

@objc func runTimer(){ 
    NSLog("Timer is running in mainThread: \(Thread.isMainThread)") 
} 

Si el temporizador se invalida en el futuro, la ejecución del programa de pausa de nuevo en Xcode, se encuentra que el hilo se ha ido.

Por supuesto, los hilos enviados por GCD tienen runloop. GCD genera y reutiliza hilos internamente, allí los hilos son anónimos para la persona que llama. Si no te sientes seguro, podrías usar Thread. No tengas miedo, el código es muy fácil.

En realidad, intento lo mismo la semana pasada y obtengo el mismo error con asker, luego encontré esta página. Intento NSThread antes de darme por vencido. Funciona. Entonces, ¿por qué NSTimer en GCD no puede funcionar? Debería ser. Lee runloop's document para saber cómo funciona NSTimer.

Uso NSThread trabajar con NSTimer:

func configurateTimerInBackgroundThread(){ 
    let thread = Thread.init(target: self, selector: #selector(addTimerInBackground), object: nil) 
    thread.name = "BackgroundThreadWithTimer" 
    thread.start() 
} 

@objc func addTimerInBackground() { 
    self.weakTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(runTimer), userInfo: nil, repeats: true) 
    RunLoop.current.run(mode: .defaultRunLoopMode, before: Date.distantFuture) 
}