Рабочий процесс 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.