Подтвердить что ты не робот

NSManagedObjectContext performBlockAndWait: не выполняется в фоновом потоке?

У меня есть NSManagedObjectContext, объявленный так:

- (NSManagedObjectContext *) backgroundMOC {
    if (backgroundMOC != nil) {
        return backgroundMOC;
    }
    backgroundMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    return backgroundMOC;
}

Обратите внимание, что он объявлен с типом частной очереди concurrency, поэтому его задачи должны выполняться в фоновом потоке. У меня есть следующий код:

-(void)testThreading
{
    /* ok */
    [self.backgroundMOC performBlock:^{
        assert(![NSThread isMainThread]); 
    }];

    /* CRASH */
    [self.backgroundMOC performBlockAndWait:^{
        assert(![NSThread isMainThread]); 
    }];
}

Почему вызов performBlockAndWait выполняет задачу в основном потоке, а не в фоновом потоке?

4b9b3361

Ответ 1

Отбрасывая другой ответ, попробуйте объяснить, почему performBlockAndWait всегда будет выполняться в вызывающем потоке.

performBlock полностью асинхронен. Он всегда ставит очередь в очередь на очередь принимающего MOC, а затем немедленно возвращается. Таким образом,

[moc performBlock:^{
    // Foo
}];
[moc performBlock:^{
    // Bar
}];

разместит два блока в очереди для moc. Они будут выполняться асинхронно. Некоторая неизвестная нить вытащит блоки из очереди и выполнит их. Кроме того, эти блоки обернуты в свой собственный пул автозаполнения, а также они будут представлять полное событие пользователя Core Data (processPendingChanges).

performBlockAndWait НЕ использует внутреннюю очередь. Это синхронная операция, выполняемая в контексте вызывающего потока. Конечно, он будет ждать, пока текущие операции в очереди не будут выполнены, а затем этот блок будет выполняться в вызывающем потоке. Это документировано (и подтверждено в нескольких презентациях WWDC).

Кроме того, performBockAndWait является повторным, так что вложенные вызовы происходят в этом вызывающем потоке.

Инженеры Core Data были очень понятны, что фактический поток, в котором выполняется операция MOC на основе очереди, не имеет значения. Это синхронизация с помощью ключа performBlock* API.

Итак, рассмотрим "executeBlock" как "Этот блок помещается в очередь, который должен быть выполнен в какое-то неопределенное время, в некотором неопределенном потоке. Функция вернется к вызывающему абоненту, как только он будет установлен в очередь"

performBlockAndWait: "Этот блок будет выполнен в течение некоторого неопределенного времени в этом же потоке. Функция вернется после полного выполнения этого кода (что произойдет после того, как текущая очередь, связанная с этим MOC, разрядилась)."

ИЗМЕНИТЬ

Вы уверены, что "executeBlockAndWait НЕ использует внутреннюю очередь"? Я думаю, что так и есть. Единственное различие заключается в том, что performBlockAndWait будет дождитесь завершения блока. И что вы имеете в виду, позвонив нить? По моему мнению, [moc performBlockAndWait] и [moc performBloc] запускаются в своей частной очереди (основной или основной). Важным понятием здесь является moc, которому принадлежит очередь, а не наоборот вокруг. Пожалуйста, поправьте меня, если я ошибаюсь. - Philip007

К сожалению, я сформулировал ответ так, как я, потому что, взятый сам по себе, это неверно. Однако в контексте исходного вопроса это правильно. В частности, при вызове performBlockAndWait в частной очереди блок будет выполняться в потоке, вызывающем функцию, - он не будет помещен в очередь и выполнен в "частном потоке".

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

performBlockAndWait использует GCD, но также имеет свой собственный слой (например, для предотвращения взаимоблокировок). Если вы посмотрите на код GCD (который является открытым исходным кодом), вы можете увидеть, как работают синхронные вызовы - и в общем случае они синхронизируются с очередью и вызывают блок в потоке, который вызывает эту функцию, - если очередь не является главной очередью или глобальная очередь. Кроме того, на переговорах WWDC инженеры Core Data подчеркивают, что performBlockAndWait будет работать в вызывающем потоке.

Итак, когда я говорю, что он не использует внутреннюю очередь, это не означает, что он вообще не использует структуры данных. Он должен синхронизировать вызов с блоками, уже находящимися в очереди, и теми, которые представлены в других потоках и других асинхронных вызовах. Однако при вызове performBlockAndWait он не помещает блок в очередь... вместо этого он синхронизирует доступ и запускает представленный блок в потоке, который вызывает функцию.

Теперь SO не является хорошим форумом для этого, потому что это немного сложнее, чем это, особенно w.r.t в главной очереди и глобальных очередях GCD, - но последнее не важно для Core Data.

Главное, что когда вы вызываете любую функцию performBlock* или GCD, вы не должны ожидать, что она будет работать в любом конкретном потоке (за исключением того, что связано с основным потоком), потому что очереди не являются потоками, а только основной очереди будут запускать блоки по определенному потоку.

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

Я надеюсь, что это имеет смысл, хотя это, вероятно, просто вызвало больше путаницы.

ИЗМЕНИТЬ

Кроме того, вы можете видеть невысказанные последствия этого, поскольку способ, которым performBlockAndWait обеспечивает поддержку повторного входа, нарушает порядок блоков FIFO. В качестве примера...

[context performBlockAndWait:^{
    NSLog(@"One");
    [context performBlock:^{
        NSLog(@"Two");
    }];
    [context performBlockAndWait:^{
        NSLog(@"Three");
    }];
}];

Обратите внимание, что строгое соблюдение гарантии FIFO очереди будет означать, что вложенные performBlockAndWait ( "Три" ) будут выполняться после асинхронного блока ( "Два" ), поскольку он был отправлен после отправки асинхронного блока. Однако это не так, как это было бы невозможно... по той же причине возникает тупик с вложенными вызовами dispatch_sync. Просто что-то нужно знать, если вы используете синхронную версию.

В общем, избегайте синхронных версий, когда это возможно, потому что dispatch_sync может вызвать тупик, а любая повторная версия, например performBlockAndWait, должна будет принять какое-то "плохое" решение для ее поддержки... например, иметь версии синхронизации "прыгать" в очередь.

Ответ 2

Почему бы и нет? Парадигма Grand Central Dispatch concurrency (которую я предполагаю, что MOC использует внутренне) сконструирована так, что только потоки и операционная система должны беспокоиться о потоках, а не разработчике (потому что ОС может сделать это лучше, чем вы можете сделать, чтобы иметь больше Подробная информация). Слишком много людей считают, что очереди такие же, как потоки. Они не.

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

В этой последней части я не уверен, правильно ли я помню, но в видеороликах WWDC 2011 о GCD я считаю, что было упомянуто, что среда выполнения попытается запустить основной поток, если это возможно, для операций синхронизации потому что он более эффективен. В конце концов, хотя, я полагаю, на ответ "почему" могут ответить только люди, которые разработали систему.

Ответ 3

Я не думаю, что MOC обязана использовать фоновый поток; он просто обязан гарантировать, что ваш код не столкнется с проблемами concurrency с MOC, если вы используете performBlock: или performBlockAndWait:. Поскольку performBlockAndWait: должен блокировать текущий поток, кажется разумным запустить этот блок в этом потоке.

Ответ 4

Вызов performBlockAndWait: только гарантирует, что вы выполните код таким образом, чтобы вы не вводили concurrency (то есть на 2 потока performBlockAndWait: не запускались одновременно, они будут блокировать друг друга).

Долгое и короткое из этого состоит в том, что вы не можете зависеть от того, в каком потоке работает операция MOC, и в основном в любом случае. Я усердно изучил, что если вы используете GCD или просто прямолинейные потоки, вам всегда нужно создавать локальные MOC для каждой операции, а затем объединять их с ведущим MOC.

Существует большая библиотека (MagicalRecord), которая делает этот процесс очень простым.