Рабочий процесс NSOperation и NSOperationQueue vs main thread
Мне нужно выполнить серию операций загрузки и записи базы данных в моем приложении. Я использую NSOperation
и NSOperationQueue
для этого.
Это сценарий приложения:
- Получить все почтовые индексы с места.
- Для каждого почтового индекса выберите все дома.
- Для каждого дома выберите данные о жителях.
Как я уже сказал, я определил NSOperation
для каждой задачи. В первом случае (Task1) я отправляю запрос на сервер для получения всех почтовых индексов. Делегат в NSOperation
получит данные. Эти данные затем записываются в базу данных. Операция базы данных определяется в другом классе. Из класса NSOperation
я делаю вызов функции записи, определенной в классе базы данных.
Мой вопрос: происходит ли операция записи базы данных в основном потоке или в фоновом потоке? Поскольку я называл это в NSOperation
, я ожидал, что он будет работать в другом потоке (Not MainThread) как NSOperation
. Может кто-то объяснить этот сценарий, имея дело с NSOperation
и NSOperationQueue
.
Ответы
Ответ 1
Мой вопрос заключается в том, происходит ли операция записи базы данных в основном нить или в фоновом потоке?
Если вы создаете NSOperationQueue
с нуля, как в:
NSOperationQueue *myQueue = [[NSOperationQueue alloc] init];
Он будет в фоновом потоке:
Операционные очереди обычно предоставляют потоки, используемые для запуска их операции. В OS X v10.6 и более поздних версиях операционные очереди используют Библиотека libdispatch (также известная как Grand Central Dispatch) для запуска выполнение их операций. В результате операции всегда выполняются в отдельном потоке, независимо от того, являются ли они обозначенные как параллельные или неконкурентные операции
Если вы не используете mainQueue
:
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
Вы также можете увидеть код следующим образом:
NSOperationQueue *myQueue = [[NSOperationQueue alloc] init];
[myQueue addOperationWithBlock:^{
// Background work
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// Main thread work (UI usually)
}];
}];
И версия GCD:
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void)
{
// Background work
dispatch_async(dispatch_get_main_queue(), ^(void)
{
// Main thread work (UI usually)
});
});
NSOperationQueue
дает более точный контроль с тем, что вы хотите сделать. Вы можете создавать зависимости между двумя операциями (загрузка и сохранение в базу данных). Чтобы передать данные между одним блоком и другим, вы можете предположить, например, что NSData
будет поступать с сервера так:
__block NSData *dataFromServer = nil;
NSBlockOperation *downloadOperation = [[NSBlockOperation alloc] init];
__weak NSBlockOperation *weakDownloadOperation = downloadOperation;
[weakDownloadOperation addExecutionBlock:^{
// Download your stuff
// Finally put it on the right place:
dataFromServer = ....
}];
NSBlockOperation *saveToDataBaseOperation = [[NSBlockOperation alloc] init];
__weak NSBlockOperation *weakSaveToDataBaseOperation = saveToDataBaseOperation;
[weakSaveToDataBaseOperation addExecutionBlock:^{
// Work with your NSData instance
// Save your stuff
}];
[saveToDataBaseOperation addDependency:downloadOperation];
[myQueue addOperation:saveToDataBaseOperation];
[myQueue addOperation:downloadOperation];
Изменить: Почему я использую ссылку __weak
для Операций, можно найти здесь. Но в двух словах следует избегать циклов сохранения.
Ответ 2
Если вы хотите выполнить операцию записи базы данных в фоновом потоке, вам нужно создать NSManagedObjectContext
для этого потока.
Вы можете создать фон NSManagedObjectContext
в методе запуска вашего соответствующего подкласса NSOperation
.
Проверьте документы Apple для Concurrency с основными данными.
Вы также можете создать NSManagedObjectContext
, который выполняет запросы в своем фоновом потоке, создав его с помощью NSPrivateQueueConcurrencyType
и выполнив запросы внутри своего метода performBlock:
.
Ответ 3
Строка выполнения NSOperation зависит от NSOperationQueue
, где вы добавили операцию. Посмотрите это выражение в своем коде -
[[NSOperationQueue mainQueue] addOperation:yourOperation]; // or any other similar add method of NSOperationQueue class
Все это предполагает, что вы еще не выполнили нить в main
методе NSOperation
, который является фактическим монстром, в котором записаны рабочие инструкции (которые ожидаются).
Однако в случае одновременных операций сценарий отличается. Очередь может порождать поток для каждой параллельной операции. Хотя это не гарантировано, и это зависит от системных ресурсов и требований ресурса работы в тот момент в системе. Вы можете управлять concurrency рабочей очереди свойством maxConcurrentOperationCount
.
ИЗМЕНИТЬ -
Я нашел ваш вопрос интересным и сделал сам анализ/журнал. У меня NSOperationQueue создан в основном потоке, как это -
self.queueSendMessageOperation = [[[NSOperationQueue alloc] init] autorelease];
NSLog(@"Operation queue creation. current thread = %@ \n main thread = %@", [NSThread currentThread], [NSThread mainThread]);
self.queueSendMessageOperation.maxConcurrentOperationCount = 1; // restrict concurrency
И затем я продолжил создание NSOperation и добавил его с помощью addOperation. В основном методе этой операции, когда я проверил текущий поток,
NSLog(@"Operation obj = %@\n current thread = %@ \n main thread = %@", self, [NSThread currentThread], [NSThread mainThread]);
это был не основной поток. И обнаружил, что текущий объект потока не является объектом основного потока.
Таким образом, пользовательское создание очереди в основном потоке (без concurrency среди его операций) не обязательно означает, что операции будут выполняться последовательно по основному потоку.
Ответ 4
Из NSOperationQueue
В iOS 4 и более поздних операционных очередях используется функция Grand Central Dispatch для выполнения операций. До iOS 4 они создают отдельные потоки для неконкурентных операций и запускают параллельные операции из текущего потока.
Итак,
[NSOperationQueue mainQueue] // added operations execute on main thread
[NSOperationQueue new] // post-iOS4, guaranteed to be not the main thread
В вашем случае вы можете создать свой собственный "поток базы данных" путем подкласса NSThread
и отправить ему сообщения с помощью performSelector:onThread:
.
Ответ 5
Сводка из документов - operations are always executed on a separate thread
(post iOS 4 подразумевает операционные очереди GCD).
Это тривиально, чтобы проверить, что он действительно работает на основном потоке:
NSLog(@"main thread? %@", [NSThread isMainThread] ? @"YES" : @"NO");
При запуске в потоке тривиально использовать GCD/libdispatch для запуска чего-либо в основном потоке, будь то основные данные, пользовательский интерфейс или другой код, необходимый для запуска в основном потоке:
dispatch_async(dispatch_get_main_queue(), ^{
// this is now running on the main thread
});
Ответ 6
Если вы делаете какие-либо нетривиальные потоки, вы должны использовать FMDatabaseQueue.