12

This is my issue. When my application enters background I want it to perform a function after certain period of time. This is what I do:

- (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 is declared in myAppDelegate.h file like this:

UIBackgroundTaskIdentifier taskIdentifier;

Everything works as it supposed to, I see that console prints HELLO just right after 30 seconds are gone. But I don't want doSomething to be executed if the app enters foreground until 30 seconds are over. So I need to cancel it. This is how i do that:

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

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

But unfortunately it doesn't cancel doSomething, it is still performed. What am I doing wrong? How do I cancel that function?

4

11 回答 11

14

为什么还要使用 GCD?当您的应用程序返回到前台时,您可以只使用NSTimer并使其无效。

于 2012-09-18T11:36:50.807 回答
12

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

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

[self performSelector:@selector(myDelayedMethod) withObject: self afterDelay: desiredDelay];
于 2015-02-04T14:17:54.680 回答
10

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

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

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

于 2016-06-15T08:41:44.290 回答
6

这个答案必须在这里发布:取消 dispatch_after() 方法?,但这是作为重复关闭的(实际上不是)。无论如何,这是谷歌为“dispatch_after cancel”返回的地方,所以......

这个问题非常基础,我敢肯定有些人想要一个真正通用的解决方案,而无需求助于各种平台特定的东西,如运行循环计时器、包含实例的布尔值和/或重块魔法。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
    }
}

而已。

要点是“所有者”应该收集句柄,直到它不再需要它们为止。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 的动态内存开销绝对很小,后者保留了要提交的实际块数组并在适当时将它们出列。
  • 你可能注意到 shouldCall 和 shouldFree 实际上是同一个倒置标志。但是如果这些不依赖于“自我”或其他与所有者相关的数据,您的所有者实例可能会传递所有权甚至 -[dealloc] 本身而不实际取消有效负载块。这可以通过 dispatch_cancel_h() 的附加 shouldCallAnyway 参数来实现。
  • 警告说明:此解决方案还缺少 didXYZ 标志的同步,并可能导致控制块和取消例程之间的竞争。使用 OSAtomicOr32Barrier() & co 进行同步。
于 2015-05-14T23:27:25.853 回答
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()
}
于 2016-10-23T18:35:14.493 回答
3

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

dispatch_after(popTime, dispatch_get_global_queue(...), ^(void) {
    if (isRunningInBackground) {
        [self doSomething];
    }
    [[UIApplication sharedApplication] endBackgroundTask:taskIdentifier];
});
于 2012-09-18T11:25:24.547 回答
2

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

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

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

  });
于 2012-09-18T11:27:27.140 回答
2

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+)

于 2019-05-28T13:13:18.473 回答
1

这是一个更通用的回答,尽管我认为它仍然可以很好地回答您的问题。而不是“isRunningInBackground”,而是保持你上次后台/前台的时间;使用您在后台运行的时间作为 dispatch_after 的局部变量。在调用 doSomething 之前检查你的 dispatch_after。下面是我更具体的问题....

我正在制作一长串需要在不同时间启动的动画,如果我使用 setBeginTime 同时确保模型层在正确的时间更新到表示层等,它们会相互踩踏……所以我开始了使用 dispatch_after,除了不能“取消”它们(这对我来说很重要,尤其是当我想重新启动一系列动画时)。

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

startCalled = CACurrentMediaTime();
CFTimeInterval thisStartCalled = startCalled;

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

if (thisStartCalled != startCalled) return;

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

于 2016-07-19T23:44:16.550 回答
1

您绝对可以用标志取消它。我写了一个小函数来做,基本上我们传递一个 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];
    }
}
于 2016-05-06T23:49:53.463 回答
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);
于 2021-04-06T11:12:22.457 回答