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

Различия в поведении между executeBlock: и performBlockAndWait:?

Я создаю NSManagedObjectContext в частной очереди для обработки обновлений данных, которые я беру из файлов и/или служб:

NSManagedObjectContext *privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
privateContext.persistentStoreCoordinator = appDelegate.persistentStoreCoordinator;

Поскольку я использую приватную очередь, я не совсем понимаю разницу между методами performBlock: и performBlockAndWait:... Для выполнения моих обновлений данных я сейчас делаю это:

[privateContext performBlock: ^{

        // Parse files and/or call services and parse
        // their responses

        // Save context
        [privateContext save:nil];

        dispatch_async(dispatch_get_main_queue(), ^{
            // Notify update to user
        });
    }];

В этом случае мои обновления данных выполняются синхронно и последовательно, поэтому я полагаю, что это правильное место для сохранения контекста, не так ли? Если я сделаю что-то не так, я буду признателен, если вы сообщите мне об этом. С другой стороны, будет ли этот код эквивалентен?:

[privateContext performBlockAndWait: ^{

        // Parse files and/or call services and parse
        // their responses

        // Save context
        [privateContext save:nil];
    }];

// Notify update to user

Снова я предполагаю, что это правильное место для сохранения контекста... Каковы различия между обоими методами (если они есть, в данном случае)?

Что делать, если вместо выполнения синхронных служебных вызовов или разбора файлов мне необходимо выполнить асинхронные вызовы служб? Как управлять этими обновлениями данных?

Заранее спасибо

4b9b3361

Ответ 1

Вы правы в том, что все, что вы хотите сделать с MOC, должно выполняться либо в performBlock, либо performBlockAndWait. Обратите внимание, что сохранение/освобождение является потокобезопасным для управляемых объектов, поэтому вам не обязательно находиться внутри одного из этих блоков, чтобы удерживать/освобождать ссылки на управляемые объекты.

Оба используют синхронную очередь для обработки сообщений, что означает, что за один раз будет выполняться только один блок. Ну, это почти так. См. Описания performBlockAndWait. В любом случае доступ к MOC будет сериализован таким образом, чтобы только один поток обращался к MOC за раз.

tl; dr Не беспокойтесь о различии и всегда используйте performBlock.

Фактические различия

Существует несколько отличий. Я уверен, что есть больше, но вот те, которые, как мне кажется, наиболее важны для понимания.

Синхронный и асинхронный

performBlock является асинхронным, поскольку он немедленно возвращается, и блок выполняется в будущем в некотором нераскрытом потоке. Все блоки, переданные MOC через performBlock, будут выполняться в том порядке, в котором они были добавлены.

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

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

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

Реентерабельность

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

[moc performBlock:^{
    doSomething();
    [moc performBlock:^{
      doSomethingElse();
    }];
    doSomeMore();
}];

Эти функции всегда будут выполняться в следующем порядке:

doSomething()
doSomeMore()
doSomethingElse()

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

[moc performBlockAndWait:^{
    doSomething();
    [moc performBlockAndWait:^{
      doSomethingElse();
    }];
    doSomeMore();
}];

Эти функции всегда будут выполняться в следующем порядке:

doSomething()
doSomethingElse()
doSomeMore()

FIFO

FIFO означает "First In First Out", что означает, что блоки будут выполняться в том порядке, в котором они были помещены во внутреннюю очередь.

performBlock всегда оценивает структуру FIFO внутренней очереди. Каждый блок будет вставлен в очередь и запускаться только при его удалении в порядке FIFO.

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

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

[moc performBlock:^{
    doSomething();
    [moc performBlock:^{
      doSomethingElse();
    }];
    doSomeMore();
    [moc performBlockAndWait:^{
      doSomethingAfterDoSomethingElse();
    }];
    doTheLastThing();
}];

Эти функции всегда будут выполняться в следующем порядке:

doSomething()
doSomeMore()
doSomethingAfterDoSomethingElse()
doTheLastThing()
doSomethingElse()

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

Следует помнить, что performBlockAndWait является превентивным и может переходить в очередь FIFO.

Тупик

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

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

Пользовательские события

Core Data рассматривает "пользовательское событие" как нечто между вызовами processPendingChanges. Вы можете прочитать сведения о том, почему этот метод важен, но то, что происходит в "пользовательском событии", имеет последствия для уведомлений, отмены управления, удаления распространения, изменения коалесценции и т.д.

performBlock инкапсулирует "пользовательское событие" , что означает, что блок кода автоматически выполняется между различными вызовами на processPendingChanges.

performBlockAndWait не инкапсулирует "пользовательское событие" . Если вы хотите, чтобы блок обрабатывался как отдельное пользовательское событие, вы должны сделать это самостоятельно.

Пул автоматического выпуска

performBlock обертывает блок в свой собственный autoreleasepool.

performBlockAdWait не предоставляет уникальный autoreleasepool. Если вам это нужно, вы должны предоставить его сами.

Личное мнение

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

Самый близкий вызов performBlockAndWait в родительском контексте (никогда не делайте этого на MOC NSMainConcurrencyType, потому что он может заблокировать ваш пользовательский интерфейс). Например, если вы хотите убедиться, что база данных полностью сохранена на диске до того, как текущий блок вернется, а другие блоки получат возможность запуска.

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

Жизнь намного лучше. У меня гораздо меньше проблем, чем я, когда думал, что "это должно быть полезно, или они не предоставили бы его".

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