Запретить выполнение фоновой задачи dispatch after()

это моя проблема. Когда мое приложение входит в фон, я хочу, чтобы оно выполняло функцию через определенный период времени. Вот что я делаю:--7-->

- (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 секунд. Но я не хочу!--4--> для выполнения, если приложение выходит на передний план до 30 секунд. Так что мне нужно отменить его. Вот как я это делаю:

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

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

но, к сожалению, это не отменяет doSomething, он все еще выполняется. Что я делаю не так? Как отменить эту функцию?

9 ответов


зачем вообще использовать GCD? Вы можете просто использовать NSTimer и аннулировать его, когда ваше приложение возвращается в foregound.


немного другой подход Итак, со всеми собранными ответами и возможными решениями, кажется, что лучшим для этого случая (сохраняя простоту) является вызов performSelector:withObject:afterDelay: и отменить его с cancelPreviousPerformRequestsWithTarget: звонок при желании. В моем случае - непосредственно перед планированием следующего отложенного звонка:

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

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

Я ответил на вопрос об отмене dispatch_after здесь. Но когда я google, чтобы найти решение, он также возвращает меня в эту тему, поэтому...

iOS 8 и OS X Yosemite введены dispatch_block_cancel, которые позволяют отменить блок, прежде чем они начнут выполняться. Вы можете просмотреть подробную информацию об этом ответе здесь

используя dispatch_after получите выгоду от использования переменных, которые вы создали в этой функции, и выглядите бесшовными. Если вы используете NSTimer затем вы должны создать Selector и отправить переменные, которые вам нужны в userInfo или превратить эти переменные в глобальные переменные.


этот ответ должен быть размещен здесь: отменить метод 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
    }
}

вот именно.

главное, что "владелец" должен собирать дескрипторы, пока они ему больше не понадобятся. 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, которые сохраняют фактический массив блоков для отправки и удаления их при необходимости.
  • вы можете заметить, что shouldCall и shouldFree действительно такой же перевернутый флаг. Но ваш экземпляр владельца может передать право собственности и даже- [dealloc] сам, фактически не отменяя блоки полезной нагрузки, если они не зависят от "себя" или других данных, связанных с владельцем. Это может реализовано с дополнительным аргументом shouldCallAnyway для dispatch_cancel_h ().
  • предупреждение: этому решению также не хватает синхронизации флагов didXYZ и может вызвать гонку между блоком управления и процедурой отмены. Используйте 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 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()
}

Это несколько более общий ответ, хотя я думаю, что он все еще отвечает на ваш вопрос достаточно хорошо. Вместо "isRunningInBackground" сохраните время последнего backgrounded/foregrounded; используйте время, когда вы были backgrounding в качестве локальной переменной для dispatch_after. Проверьте внутри вашего dispatch_after перед вызовом doSomething. Моя более конкретная проблема ниже....

я делаю длинную кучу анимаций, которые нужно запускать в разное время и топать по всему каждому другое, если я использовал setBeginTime, убедившись, что слой модели обновлен до уровня презентации в нужное время и т. д... поэтому я начал использовать dispatch_after, за исключением того, что не мог" отменить " их (что имело значение для меня, особенно когда я хотел перезапустить серию анимаций).

Я CFTimeInterval startCalled; на моем экземпляре UIView, а затем внутри моего -(void) start Я:

startCalled = CACurrentMediaTime();
CFTimeInterval thisStartCalled = startCalled;

в начале каждого dispatch_after блок, у меня тогда есть:

if (thisStartCalled != startCalled) return;

Это позволяет мне настройте все за один раз, но только обновите мои слои модели внутри своих блоков CATransaction at время они должны начать.