防止dispatch_after()后台任务被执行

时间:2012-09-18 10:46:19

标签: ios cocoa-touch grand-central-dispatch

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

- (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变量在myAppDelegate.h文件中声明如下:

UIBackgroundTaskIdentifier taskIdentifier;

一切都按照预期运行,我看到控制台在30秒后就打印了HELLO。但是如果应用程序进入前景直到30秒结束,我不希望执行doSomething。所以我需要取消它。这就是我这样做的方式:

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

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

但不幸的是它没有取消doSomething,它仍在执行。我究竟做错了什么?如何取消该功能?

11 个答案:

答案 0 :(得分:13)

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

答案 1 :(得分:12)

有点不同的方法 好的,所以,收集了所有答案,并且可能的解决方案似乎是这种情况下最好的解决方案(保持简洁性)是调用performSelector:withObject:afterDelay:并在需要时使用cancelPreviousPerformRequestsWithTarget:调用取消它。就我而言 - 就在安排下一次延迟通话之前:

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

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

答案 2 :(得分:9)

我回答了有关取消dispatch_after here的问题。但是当我谷歌找到解决方案时,它也会让我回到这个主题,所以......

iOS 8和OS X Yosemite推出dispatch_block_cancel,允许您在开始执行之前取消阻止。您可以查看有关该答案的详细信息here

使用dispatch_after可以获得有关使用在该函数中创建的变量并且看起来无缝的好处。如果您使用NSTimer,则必须创建Selector并将所需的变量发送到userInfo或将变量转换为全局变量。

答案 3 :(得分:6)

这个答案必须在这里发布:cancel dispatch_after() method?,但这是一个重复的关闭(它真的不是)。无论如何,这是一个谷歌返回的地方" dispatch_after取消",所以......

这个问题非常重要,我确信有些人想要一个真正通用的解决方案,而不需要使用各种特定平台,如runloop计时器,实例包含的布尔值和/或重块魔术。 GCD可以用作常规C库,并且可能根本没有计时器。

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

  1. 我们必须为每个传递给dispatch_after(或者dispatch_async,不重要)的块附加一个动态句柄。
  2. 此句柄必须存在,直到实际触发该块为止。
  3. 此句柄的内存管理不是那么明显 - 如果块释放句柄,那么我们可以稍后取消引用悬空指针,但是如果我们释放它,则阻塞可能会在以后执行。
  4. 因此,我们必须按要求转让所有权。
  5. 有2个块 - 一个是控制块无论如何都会触发,第二个是可以取消的有效载荷
  6. 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。
    • async_handle的动态内存开销与dispatch_after()和dispatch_queue_t的内部结构相比是绝对较小的,它们保留了一个实际的块数组,并在适当时将它们出列。< / LI>
    • 您可能会注意到,shouldCall和shouldFree实际上是相同的倒置标志。但是你的所有者实例可以传递所有权甚至 - [dealloc]本身而不实际取消有效负载块,如果这些不依赖于&#34; self&#34;或其他所有者相关数据。这可以通过dispatch_cancel_h()。
    • 的附加shouldCallAnyway参数来实现
    • 警告说明:此解决方案还缺少didXYZ标志的同步,并可能导致控制块和取消例程之间的竞争。使用OSAtomicOr32Barrier()&amp; co同步。

答案 4 :(得分:3)

endBackgroundTask不会取消后台任务。它告诉系统您的后台任务已完成。所以你应该在“做某事”之后再打电话。如果您的应用再次位于前台,为了防止执行doSomething,您可以使用isRunningInBackground标记:

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

答案 5 :(得分:3)

由于 iOS 10 Swift 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()
}

答案 6 :(得分:2)

我认为您无法取消它,但您可以在执行doSomething之前检查任务状态

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

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

  });

答案 7 :(得分:1)

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

openssl rsa -in openssl.priv -pubout >openssl.pub

答案 8 :(得分:1)

https://developer.apple.com/documentation/dispatch/1431058-dispatch_block_cancel

dispatch_block_cancel 异步取消指定的调度块。

void dispatch_block_cancel(dispatch_block_t block);

(iOS 8.0 +,macOS 10.10 +,tvOS 9.0 +,watchOS 2.0 +)

答案 9 :(得分:0)

这是一个更为通用的回应,虽然我认为它仍然可以很好地回答你的问题。而不是&#34; isRunningInBackground&#34;保持你上次背景/前庭的时间;将您作为背景的时间用作dispatch_after的局部变量。在调用doSomething之前检查dispatch_after内部。我下面更具体的问题....

我做了一大堆需要在不同时间启动的动画,如果我使用setBeginTime同时确保模型层在正确的时间更新到表示层等,那么它们会互相踩踏,等等。 ..所以我开始使用dispatch_after,除了没有&#t; t&#34;取消&#34;它们(这对我来说很重要,特别是当我想重新启动一系列动画时)。

我在我的UIView实例上保留CFTimeInterval startCalled;,然后在我的-(void) start内部:

startCalled = CACurrentMediaTime();
CFTimeInterval thisStartCalled = startCalled;

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

if (thisStartCalled != startCalled) return;

这让我可以一次性设置所有内容,但只有我们的模型图层在他们应该开始的时间 的CATransaction块内更新。

答案 10 :(得分:0)

我使用以下方法在不阻塞主线程的情况下对搜索进行去抖动。

创建一个全局变量来存储块

dispatch_block_t searchBlock;

检查它是否存在并取消它(仅当它尚未发送时)

   if (searchBlock) {
        dispatch_block_cancel(searchBlock);
    }

定义块

    searchBlock = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS, ^{
        //Do Something
    });

执行

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), serialQueue, searchBlock);