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

And*_*kha 12 cocoa-touch grand-central-dispatch ios

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

- (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");
}
Run Code Online (Sandbox Code Playgroud)

taskIdentifier 变量在myAppDelegate.h文件中声明如下:

UIBackgroundTaskIdentifier taskIdentifier;
Run Code Online (Sandbox Code Playgroud)

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

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

- (void)stopBackgroundExecution
{
    [[UIApplication sharedApplication] endBackgroundTask:taskIdentifier];
    taskIdentifier = UIBackgroundTaskInvalid;
}
Run Code Online (Sandbox Code Playgroud)

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

Ste*_*ton 13

为什么甚至使用GCD?NSTimer当你的应用程序返回到foregound时,你可以使用并使其无效.


Ole*_*yuk 12

有点不同的方法 OK,因此,收集所有答案,以及可能的解决方案,对于这种情况(保持简单性)似乎是最好的方法是在需要时performSelector:withObject:afterDelay:通过cancelPreviousPerformRequestsWithTarget:调用来调用和取消它.就我而言 - 就在安排下一次延迟通话之前:

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

[self performSelector:@selector(myDelayedMethod) withObject: self afterDelay: desiredDelay];
Run Code Online (Sandbox Code Playgroud)


Qua*_*ran 10

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

iOS 8和OS X Yosemite引入dispatch_block_cancel了允许您在开始执行之前取消块.您可以在此处查看有关该答案的详细信息

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


use*_*367 6

这个答案必须在这里发布:取消dispatch_after()方法?,但这是一个副本(实际上不是).无论如何,这是一个google返回"dispatch_after cancel"的地方,所以......

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

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

  1. 我们必须将动态句柄附加到我们传递给dispatch_after(或dispatch_async,并不重要)的每个块.
  2. 在实际触发块之前,此句柄必须存在.
  3. 这个句柄的内存管理不是那么明显 - 如果块释放句柄,那么我们可以稍后取消引用悬空指针,但是如果我们释放它,则阻塞可以在以后执行.
  4. 所以,我们必须按要求转让所有权.
  5. 有2个块 - 一个是无论如何都要触发的控制块,第二个是可以取消的有效载荷.

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
    }
}
Run Code Online (Sandbox Code Playgroud)

而已.

重点是"所有者"应该收集句柄,直到它不再需要它们为止.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
}
Run Code Online (Sandbox Code Playgroud)

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.
}
Run Code Online (Sandbox Code Playgroud)

笔记:

  • dispatch_after()最初保留队列,因此它将一直存在,直到所有控制块都被执行.
  • 如果有效负载被取消(或者所有者的生命周期结束)并且执行了控制块,则释放async_handles.
  • 与dispatch_after()和dispatch_queue_t的内部结构相比,async_handle的动态内存开销非常小,它保留了一个实际的块数组,并在适当时将它们出列.
  • 您可能会注意到,shouldCall和shouldFree实际上是相同的倒置标志.但是您的所有者实例可以传递所有权甚至 - [dealloc]本身而不实际取消有效负载块,如果这些不依赖于"自我"或其他所有者相关数据.这可以通过dispatch_cancel_h()的附加shouldCallAnyway参数来实现.
  • 警告说明:此解决方案还缺少didXYZ标志的同步,并可能导致控制块和取消例程之间的竞争.使用OSAtomicOr32Barrier()&co进行同步.


Han*_*son 5

由于iOS 10Swift 3 GCDDispatchWorkItem是可取消的。只需为工作项保留一个实例并检查它是否尚未取消,然后取消它:

// 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 {
    work.cancel()
}
Run Code Online (Sandbox Code Playgroud)