阻止执行dispatch_after()后台任务

这是我的问题。 当我的应用程序进入后台,我希望它在一段时间后执行一个function。 这就是我所做的:

- (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"); } 

taskIdentifiervariables在myAppDelegate.h文件中声明如下:

 UIBackgroundTaskIdentifier taskIdentifier; 

一切都按照预想的那样工作,我看到控制台在30秒之后就打印了HELLO。 但是如果应用程序进入前景,直到30秒结束,我不想要执行某些操作。 所以我需要取消它。 这是我如何做到这一点:

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

但不幸的是,它不取消doSomething ,它仍然执行。 我究竟做错了什么? 我如何取消该function?

为什么甚至使用GCD? 您可以使用NSTimer ,并在应用程序返回到前台时使其失效。

有点不同的方法可以,因此,收集所有答案和可能的解决scheme似乎是这种情况下最好的一种(保持简单性)调用performSelector:withObject:afterDelay:并在需要时用cancelPreviousPerformRequestsWithTarget: call来取消。 在我的情况下 – 就在安排下一个延迟的呼叫之前:

 [NSObject cancelPreviousPerformRequestsWithTarget: self selector:@selector(myDelayedMethod) object: self]; [self performSelector:@selector(myDelayedMethod) withObject: self afterDelay: desiredDelay]; 

我在这里回答了关于取消dispatch_after的问题。 但是当我谷歌find一个解决scheme,它也返回到这个线程,所以…

iOS 8和OS X Yosemite引入了dispatch_block_cancel ,允许您在开始执行之前取消块。 你可以在这里查看关于这个答案的细节

使用dispatch_after获得有关使用您在该函数中创build的variables并看起来无缝的好处。 如果您使用NSTimer那么您必须创build一个Selector ,并将需要的variables发送到userInfo或将该variables转换为全局variables。

这个答案必须贴在这里: 取消dispatch_after()方法? ,但这是封闭的重复(实际上不是)。 无论如何,这是一个地方,谷歌返回“dispatch_after取消”,所以…

这个问题是非常基础的,我敢肯定,有些人想要一个真正的通用解决scheme,而不是诉诸像runloop定时器,实例包含的布尔和/或沉重的块魔法的各种特定平台。 GCD可能被用作一个普通的C库,并且可能根本就没有定时器这样的东西。

幸运的是,有一种方法可以在任何生命周期scheme中取消任何调度块。

  1. 我们必须为每个传递给dispatch_after(或dispatch_async,实际上并不重要)的块附加一个dynamic句柄。
  2. 该句柄必须存在,直到块被实际触发。
  3. 这个句柄的内存pipe理并不是那么明显 – 如果块释放句柄,那么我们可以稍后解除悬挂指针的引用,但是如果我们释放它,块可以稍后再做。
  4. 所以,我们必须通过所有权的要求。
  5. 有两个块 – 一个是无论如何都会触发的控制块 ,另一个是可能被取消的有效载荷

 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 } } 

而已。

重点是“业主”应该收集手柄,直到它不再需要它们。 dispatch_cancel_h()作为句柄的[可能延迟]析构函数。

C主人的例子:

 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示例:

 - (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. } 

笔记:

  • dispatch_after()最初保留队列,所以它将一直存在,直到所有的控制块被执行。
  • 如果有效载荷被取消(或者所有者的生命周期已经结束)并且控制块被执行,则async_handles被释放。
  • 与dispatch_after()和dispatch_queue_t的内部结构相比,async_handle的dynamic内存开销是绝对较小的,内部结构保留要提交的实际数据块数组,并在适当的时候将它们出队。
  • 你可能会注意到应该调用和shouldFree是一样的倒转的标志。 但是,您的所有者实例可能会传递所有权,甚至是 – [dealloc]本身,而不实际取消有效载荷块,如果这些不依赖于“自我”或其他所有者相关的数据。 这可以通过dispatch_cancel_h()的其他shouldCallAnyway参数来实现。
  • 警告 :此解决scheme也缺lessdidXYZ标志的同步,并可能导致控制块和取消例程之间的竞争。 使用OSAtomicOr32Barrier()&co来同步。

endBackgroundTask不取消后台任务。 它告诉系统你的后台任务已经完成。 所以你应该在“做某事”之后调用它。 要防止doSomething被执行,如果你的应用再次处于前台,你可以使用你的isRunningInBackground标志:

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

我认为你不能取消它,但你可以在执行doSomething之前检查任务状态

 dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) { if(taskIdentifier != UIBackgroundTaskInvalid) { [self doSomething]; } }); 

你可以绝对用旗帜取消它。 我写了一个小函数来做,基本上我们传递一个BOOL指针来控制块是否被取消。

 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]; } } 

由于iOS 10Swift 3 GCD DispatchWorkItem是可取消的。 只要保留一个实例到工作项目,并检查它是否没有取消,然后取消它:

 // 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() } 

这是一个比较一般的回应,虽然我认为它仍然很好地回答你的问题。 而不是“isRunningInBackground”保持你最后一次背景/预测的时间; 使用你作为本地variables背景的时间到dispatch_after。 在调用doSomething之前检查你的dispatch_after。 我下面更具体的问题….

我做了一大堆需要在不同时间启动的animation,如果我在使用setBeginTime的同时确保在合适的时间将模型层更新到表示层等等,那么这些animation将会彼此重叠。使用dispatch_after,除了不能“取消”它们(这对我来说很重要,特别是当我想重新开始一系列animation的时候)。

我保持一个CFTimeInterval startCalled; 在我的UIView实例,然后在我的-(void) start我有:

 startCalled = CACurrentMediaTime(); CFTimeInterval thisStartCalled = startCalled; 

在每个dispatch_after块的开始,我有:

 if (thisStartCalled != startCalled) return; 

这样,我可以一次性完成所有的设置,但是只有在我们的模型图层他们应该开始的时候更新到CATransaction块的内部。