dispatch_source_cancel на приостановленном таймере вызывает EXC_BAD_INSTRUCTION

Я пытаюсь отменить, а затем сбросить приостановленный таймер, но когда я вызываю для него «dispatch_release», я немедленно получаю EXC_BAD_INSTRUCTION.

Разве это не допустимый набор действий для таймера?

Создание и приостановка таймера:

@interface SomeClass: NSObject { }
@property (nonatomic, assign) dispatch_source_t             timer;
@end

// Class implementation
@implementation SomeClass

@synthesize timer = _timer;

- (void)startTimer 
{
    dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 
                                    0, 0, globalQ); 

    dispatch_time_t startWhen = dispatch_walltime(DISPATCH_TIME_NOW, NSEC_PER_SEC * 1);
    dispatch_source_set_timer(_timer, startWhen, 1 * NSEC_PER_SEC, 5000ull);

    dispatch_source_set_event_handler(_timer, ^{
        // Perform a task 

        // If a particular amount of time has elapsed, kill this timer
        if (timeConstraintReached)
        {
            // Can I suspend this timer within it's own event handler block?
            dispatch_suspend(_timer);
        }
    });

    dispatch_resume(_timer);
}

- (void)resetTimer
{
    dispatch_suspend(_timer);

    dispatch_source_cancel(_timer);

    // dispatch_release causes 
    // 'EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
    dispatch_release(_timer);

    self.timer = nil;    
}
@end

Кроме того, могу ли я вызвать dispatch_suspend в блоке event_handler источника таймера?

Любая помощь будет оценена по достоинству.


person Jonas Gardner    schedule 05.03.2012    source источник


Ответы (1)


Причина сбоя заключается в этом коде:

void
_dispatch_source_xref_release(dispatch_source_t ds)
{
    if (slowpath(DISPATCH_OBJECT_SUSPENDED(ds))) {
        // Arguments for and against this assert are within 6705399
        DISPATCH_CLIENT_CRASH("Release of a suspended object");
    }
    _dispatch_wakeup(ds);
    _dispatch_release(ds);
}

Таким образом, вы не можете освободить dispatch_source_t, действие которого было приостановлено. Вы, вероятно, хотите просто не приостанавливать его в resetTimer, я думаю.

Хотя я ничего не могу найти в документах, почему они написали это именно так (и комментарий ссылается на плюсы и минусы, находящиеся в радаре, которого мы никогда не увидим), все, что я могу сделать, это сослаться на документы, где указано:

Вы можете временно приостановить и возобновить доставку событий источника отправки, используя методы dispatch_suspend и dispatch_resume. Эти методы увеличивают и уменьшают счетчик приостановки для вашего объекта отправки. В результате вы должны сбалансировать каждый вызов dispatch_suspend с соответствующим вызовом dispatch_resume, прежде чем доставка события возобновится.

Хотя это не говорит о том, что вы не можете освободить источник отправки, который был приостановлен, это говорит о том, что вы должны сбалансировать каждый вызов, поэтому я предполагаю, что это что-то вроде использования семафора отправки под капотом, который имеет должны быть сбалансированы до их выпуска . Хотя это только мое предположение :-).

Что касается «могу ли я вызвать dispatch_suspend в блоке event_handler источника таймера». Я почти уверен, что вы можете, да, согласно документам для dispatch_suspend:

Приостановка происходит после завершения любых блоков, запущенных во время вызова.

person mattjgalloway    schedule 05.03.2012
comment
Ах я вижу. Поскольку каждый dispatch_suspend «увеличивает счетчик приостановки объекта», должен быть «dispatch_resume», чтобы сбалансировать приостановку. - person Jonas Gardner; 05.03.2012
comment
Возможно ли отменить или приостановить таймер в его блоке event_handler или это должно произойти вне блока? - person Jonas Gardner; 05.03.2012
comment
Правильно да. Смотрите мой обновленный ответ. Хотя в документах на самом деле не говорится (что я могу найти), что вы не можете выпустить, когда он приостановлен, я подозреваю, что у него есть какое-то внутреннее состояние, которое вызывает проблемы, если вы это сделали. - person mattjgalloway; 05.03.2012
comment
@JonasGardner - снова обновленный ответ. Почти уверен, что вы можете приостановить работу внутри обработчика событий, да. Поскольку приостановка происходит после завершения любых блоков, находящихся в данный момент в очереди в момент вызова. - person mattjgalloway; 05.03.2012