2012-09-18 20 views
10

Este es mi problema. Cuando mi aplicación entra en segundo plano, quiero que realice una función después de cierto período de tiempo. Esto es lo que hago:Evitar que se ejecute la tarea en segundo plano dispatch_after()

- (void)applicationDidEnterBackground:(UIApplication *)application 
{ 
    isRunningInBackground = YES; 

    taskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil]; 

    int64_t delayInSeconds = 30; 
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); 
    dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) 
    { 
     [self doSomething]; 
    }); 
} 

- (void)doSomething 
{ 
    NSLog(@"HELLO"); 
} 

taskIdentifier variable se declara en el archivo myAppDelegate.h así:

UIBackgroundTaskIdentifier taskIdentifier; 

Todo funciona como se supone que, veo que las impresiones de la consola HOLA justo después de 30 segundos se fueron. Pero no quiero que se ejecute doSomething si la aplicación entra en primer plano hasta que hayan transcurrido 30 segundos. Entonces necesito cancelarlo. Esta es la forma en que lo hago:

- (void)applicationWillEnterForeground:(UIApplication *)application 
{  
    isRunningInBackground = NO; 
    [self stopBackgroundExecution]; 
} 

- (void)stopBackgroundExecution 
{ 
    [[UIApplication sharedApplication] endBackgroundTask:taskIdentifier]; 
    taskIdentifier = UIBackgroundTaskInvalid; 
} 

Pero, desgraciadamente, no cancela doSomething, todavía se lleva a cabo. ¿Qué estoy haciendo mal? ¿Cómo cancelo esa función?

Respuesta

13

¿Por qué incluso usar GCD? Solo podría usar un NSTimer e invalidarlo cuando su aplicación regrese al objetivo.

+0

Tiene usted razón, que es la mejor solución! –

+0

¡Muchas gracias! ¡Tan sencillo! Debería haberlo pensado –

3

endBackgroundTask no cancela una tarea en segundo plano. Le dice al sistema que su tarea de fondo ha terminado. Entonces deberías llamar a esto después de "hacer algo". Para evitar doSomething de ser ejecutado si su aplicación está en el primer plano de nuevo, usted podría utilizar su bandera isRunningInBackground:

dispatch_after(popTime, dispatch_get_global_queue(...), ^(void) { 
    if (isRunningInBackground) { 
     [self doSomething]; 
    } 
    [[UIApplication sharedApplication] endBackgroundTask:taskIdentifier]; 
}); 
+0

esta es la respuesta correcta – juancazalla

2

Creo que no se puede cancelar, pero se puede comprobar el estado de la tarea antes de ejecutar el doSomething

dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) 
    { 

    if(taskIdentifier != UIBackgroundTaskInvalid) { 
     [self doSomething]; 
    } 

    }); 
11

Un enfoque poco diferente OK, así, con todas las respuestas recogidas, y las posibles soluciones, parece que la mejor opción para este caso (simplicidad preservar) está llamando performSelector:withObject:afterDelay: y cancelando con cancelPreviousPerformRequestsWithTarget: llamada cuando se desee. En mi caso - justo antes de la programación de la próxima llamada tardía:

[NSObject cancelPreviousPerformRequestsWithTarget: self selector:@selector(myDelayedMethod) object: self]; 

[self performSelector:@selector(myDelayedMethod) withObject: self afterDelay: desiredDelay]; 
5

Esta respuesta debe ser publicado aquí: cancel dispatch_after() method?, pero que se cierra como un duplicado (en realidad no lo es). De todos modos, este es un lugar que google devuelve para "dispatch_after cancel", así que ...

Esta pregunta es bastante fundamental y estoy seguro de que hay personas que quieren una solución verdaderamente genérica sin recurrir a varias plataformas específicas como temporizadores runloop, booleanos de instancia contenida y/o magia de bloques pesados. GCD se puede usar como una biblioteca regular de C y puede que no haya ningún temporizador en absoluto.

Afortunadamente, hay una forma de cancelar cualquier bloque de despacho en cualquier esquema de por vida.

  1. Tenemos que adjuntar un control dinámico a cada bloque que pasemos a dispatch_after (o dispatch_async, en realidad no importa).
  2. Este identificador debe existir hasta que se active realmente el bloque.
  3. La gestión de la memoria para este identificador no es tan obvia: si el bloque libera el identificador, es posible que podamos desviar el puntero colgante más adelante, pero si lo liberamos, bloque puede hacerlo más tarde.
  4. Entonces, tenemos que pasar la propiedad bajo demanda.
  5. Hay 2 bloques: uno es un bloque de control que se activa de todos modos y el segundo es un carga útil que puede cancelarse.

struct async_handle { 
    char didFire;  // control block did fire 
    char shouldCall; // control block should call payload 
    char shouldFree; // control block is owner of this handle 
}; 

static struct async_handle * 
dispatch_after_h(dispatch_time_t when, 
       dispatch_queue_t queue, 
       dispatch_block_t payload) 
{ 
    struct async_handle *handle = malloc(sizeof(*handle)); 

    handle->didFire = 0; 
    handle->shouldCall = 1; // initially, payload should be called 
    handle->shouldFree = 0; // and handles belong to owner 

    payload = Block_copy(payload); 

    dispatch_after(when, queue, ^{ 
     // this is a control block 

     printf("[%p] (control block) call=%d, free=%d\n", 
      handle, handle->shouldCall, handle->shouldFree); 

     handle->didFire = 1; 
     if (handle->shouldCall) payload(); 
     if (handle->shouldFree) free(handle); 
     Block_release(payload); 
    }); 

    return handle; // to owner 
} 

void 
dispatch_cancel_h(struct async_handle *handle) 
{ 
    if (handle->didFire) { 
     printf("[%p] (owner) too late, freeing myself\n", handle); 
     free(handle); 
    } 
    else { 
     printf("[%p] (owner) set call=0, free=1\n", handle); 
     handle->shouldCall = 0; 
     handle->shouldFree = 1; // control block is owner now 
    } 
} 

Eso es todo.

El punto principal es que el "propietario" debe recoger identificadores hasta que ya no los necesite. dispatch_cancel_h() funciona como un destructor [potencialmente diferido] para un identificador.

C ejemplo propietario:

size_t n = 100; 
struct after_handle *handles[n]; 

for (size_t i = 0; i < n; i++) 
    handles[i] = dispatch_after_h(when, queue, ^{ 
     printf("working\n"); 
     sleep(1); 
    }); 

... 

// cancel blocks when lifetime is over! 

for (size_t i = 0; i < n; i++) { 
    dispatch_cancel_h(handles[i]); 
    handles[i] = NULL; // not our responsibility now 
} 

Objective-C ARC ejemplo:

- (id)init 
{ 
    self = [super init]; 
    if (self) { 
     queue = dispatch_queue_create("...", DISPATCH_QUEUE_SERIAL); 
     handles = [[NSMutableArray alloc] init]; 
    } 
    return self; 
} 

- (void)submitBlocks 
{ 
    for (int i = 0; i < 100; i++) { 
     dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (random() % 10) * NSEC_PER_SEC); 

     __unsafe_unretained id this = self; // prevent retain cycles 

     struct async_handle *handle = dispatch_after_h(when, queue, ^{ 
      printf("working (%d)\n", [this someIntValue]); 
      sleep(1); 
     }); 
     [handles addObject:[NSValue valueWithPointer:handle]]; 
    } 
} 

- (void)cancelAnyBlock 
{ 
    NSUInteger i = random() % [handles count]; 
    dispatch_cancel_h([handles[i] pointerValue]); 
    [handles removeObjectAtIndex:i]; 
} 

- (void)dealloc 
{ 
    for (NSValue *value in handles) { 
     struct async_handle *handle = [value pointerValue]; 
     dispatch_cancel_h(handle); 
    } 
    // now control blocks will never call payload that 
    // dereferences now-dangling self/this. 
} 

Notas:

  • dispatch_after() conserva originalmente la cola, por lo que existirá hasta que todo los bloques de control se ejecutan
  • async_handles se liberan si se cancela la carga útil (o se acabó la vida útil del propietario) Y se ejecutó el bloque de control.
  • La sobrecarga de memoria dinámica de async_handle es absolutamente menor en comparación con las estructuras internas de dispatch_after() y dispatch_queue_t, que conservan una matriz real de bloques que se enviarán y dequeue cuando sea apropiado.
  • Puede notar que shouldCall y shouldFree es realmente la misma bandera invertida. Pero su instancia de propietario puede pasar la propiedad e incluso - [dealloc] sí mismo sin cancelar realmente los bloques de carga útil, si estos no dependen de "sí mismo" u otros datos relacionados con el propietario. Esto podría implementarse con el argumento adicional shouldCallAnyway para dispatch_cancel_h().
  • Nota de advertencia: esta solución también carece de sincronización de banderas de didXYZ y puede causar una carrera entre el bloque de control y la rutina de cancelación. Use OSAtomicOr32Barrier() & co para sincronizar.
+0

Nota: el código en la respuesta fue escrito para ARC. En MRC o C puro, la carga útil se debe bloquear de forma explícita en dispatch_after_h() y Block_release'd en el bloque de control para corregir la desasignación prematura. – user3125367

1

Usted puede absolutamente cancelarlo con una bandera. Escribí una pequeña función para hacerlo, básicamente pasamos un puntero BOOL para controlar si el bloque se cancela.

void dispatch_with_cancellation(void (^block)(), BOOL* cancellation) { 
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC); 
    dispatch_after(time, dispatch_get_main_queue(), ^{ 
     if (!*cancellation) { 
      block(); 
     } 
    }); 
} 

int main(int argc, char *argv[]) { 
    @autoreleasepool { 
     void (^block)() = ^{ 
      NSLog(@"%@", @"inside block"); 
     }; 
     BOOL cancellation; 
     dispatch_with_cancellation(block, &cancellation); 
     // cancel the block by setting the BOOL to YES. 
     *&cancellation = YES; 
     [[NSRunLoop currentRunLoop] run]; 
    } 
} 
7

que respondieron a la pregunta sobre cancelar dispatch_afterhere. Pero cuando busco una solución en google también me devuelve a este hilo, así que ...

iOS 8 y OS X Yosemite introdujo dispatch_block_cancel que le permiten cancelar un bloque antes de que comiencen a ejecutarse. Puede ver detalles sobre esa respuesta here

Utilizando dispatch_after obtenga beneficio sobre el uso de variables que haya creado en esa función y luzca impecable. Si usa NSTimer, debe crear un Selector y enviar variables que necesite al userInfo o convertir esas variables en variables globales.

+0

Esta es en realidad la respuesta correcta ahora, ya que la respuesta original es esencialmente "No hagas eso. Hazlo de esta manera". –

0

Esta es una respuesta algo más genérica, aunque creo que todavía responde a su pregunta razonablemente bien.En lugar de "isRunningInBackground", mantenga la última vez que haya retrocedido/centrado; utilice el tiempo que estaba creando como una variable local para el dispatch_after. Verifique dentro de su dispatch_after antes de llamar a doSomething. Mi problema más específico a continuación ...

Estoy haciendo un montón de animaciones que deben lanzarse varias veces y pisaré una sobre otra si utilizo setBeginTime mientras me aseguro de que la capa del modelo se actualizó a la presentación capa en el momento adecuado, etc ... así que comencé a usar dispatch_after, excepto que no pude "cancelarlos" (lo cual me importó especialmente cuando quería reiniciar la serie de animaciones).

estoy manteniendo un CFTimeInterval startCalled; en mi ejemplo UIView, y luego dentro de mi -(void) start tengo:

startCalled = CACurrentMediaTime(); 
CFTimeInterval thisStartCalled = startCalled; 

Al comienzo de cada bloque dispatch_after, entonces me tengo:

if (thisStartCalled != startCalled) return; 

Esto me permite configurar todo de una vez, pero solo tengo mis capas de modelo actualizadas dentro de sus bloques de CATransaction en el momento en que deben comenzar.

1

Desde iOS 10 y Swift 3 GCD DispatchWorkItem son cancellable. Hemos de tener una instancia para el elemento de trabajo y comprobar si no ha sido cancelado y luego cancelarlo:

// Create a work item 
let work = DispatchWorkItem { 
    print("Work to be done or cancelled") 
} 

// Dispatch the work item for executing after 2 seconds 
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2), execute: work) 

// Later cancel the work item 
if !work.isCancelled { 
    print("Work:\(work)") 
    dispatchPrecondition(condition: .onQueue(.main)) 
    work.cancel() 
} 
Cuestiones relacionadas