Dispatch_sync в главной очереди зависает в unit test

У меня возникла проблема, связанная с тестированием какого-то большого центрального кода отправки со встроенной инфраструктурой тестирования Xcode, SenTestingKit. Мне удалось решить мою проблему. У меня есть unit test, который строит блок и пытается выполнить его в основном потоке. Тем не менее, блок никогда не выполняется, поэтому тест зависает, потому что это синхронная отправка.

- (void)testSample {

    dispatch_sync(dispatch_get_main_queue(), ^(void) {
        NSLog(@"on main thread!");
    });

    STFail(@"FAIL!");
}

Что происходит с тестовой средой, которая заставляет это зависать?

Ответ 1

dispatch_sync запускает блок в заданной очереди и ждет его завершения. В этом случае очередь является основной диспетчерской очередью. Основная очередь выполняет все свои операции над основным потоком в порядке FIFO (first-in-first-out). Это означает, что всякий раз, когда вы вызываете dispatch_sync, ваш новый блок будет помещен в конец строки и не будет выполняться до тех пор, пока все остальное до него в очереди не будет выполнено.

Проблема заключается в том, что выделенный вами блок находится в конце строки, ожидающей запуска в основном потоке, а метод testSample -, текущий в основном потоке. Блок в конце очереди не может получить доступ к основному потоку, пока текущий метод (сам) не завершит использование основного потока.

Ответ 2

Проблема в вашем коде заключается в том, что независимо от того, будете ли вы использовать dispatch_sync или dispatch_async, STFail() всегда будет вызываться, что приведет к сбою теста.

Что еще более важно, как объяснил BJ Homer, если вам нужно что-то синхронно запускать в главной очереди, вы должны убедиться, что вы не находитесь в главной очереди или произойдет блокировка блокировки. Если вы находитесь в главной очереди, вы можете просто запустить блок как регулярную функцию.

Надеюсь, что это поможет:

- (void)testSample {

    __block BOOL didRunBlock = NO;
    void (^yourBlock)(void) = ^(void) {
        NSLog(@"on main queue!");
        // Probably you want to do more checks here...
        didRunBlock = YES;
    };

    // 2012/12/05 Note: dispatch_get_current_queue() function has been
    // deprecated starting in iOS6 and OSX10.8. Docs clearly state they
    // should be used only for debugging/testing. Luckily this is our case :)
    dispatch_queue_t currentQueue = dispatch_get_current_queue();
    dispatch_queue_t mainQueue = dispatch_get_main_queue();

    if (currentQueue == mainQueue) {
        blockInTheMainThread();
    } else {
        dispatch_sync(mainQueue, yourBlock); 
    }

    STAssertEquals(YES, didRunBlock, @"FAIL!");
}

Ответ 3

Если вы находитесь в главной очереди и синхронно ожидаете, что основная очередь будет доступна, вы действительно будете долго ждать. Вы должны проверить, чтобы убедиться, что вы еще не находитесь в основном потоке.

Ответ 4

Чтобы следить за тем, чтобы

dispatch_get_current_queue()

теперь устарел, вы можете использовать

[NSThread isMainThread]

чтобы узнать, находитесь ли вы в основном потоке.

Итак, используя другой ответ выше, вы можете сделать:

- (void)testSample 
{
    BOOL __block didRunBlock = NO;
    void (^yourBlock)(void) = ^(void) {
        NSLog(@"on main queue!");
        didRunBlock = YES;
    };

    if ([NSThread isMainThread])
        yourBlock();
    else
        dispatch_sync(dispatch_get_main_queue(), yourBlock);

    STAssertEquals(YES, didRunBlock, @"FAIL!");
}

Ответ 5

Вы когда-нибудь выйдете из дома, если вы должны подождать для себя, чтобы сначала выйти из дома? Вы угадали! Нет!:]

В принципе, если:

  • Вы на FooQueue. (не должен быть main_queue)
  • Вы вызываете метод с помощью sync, то есть последовательно, и хотите выполнить на FooQueue.

По той же причине никогда не случится, что вы никогда не выйдете из дома!

Он никогда не будет отправлен, потому что он должен ждать, чтобы выйти из очереди!