NSOperation та NSOperationQueue робочий потік проти основного потоку


81

Я повинен виконати серію операцій завантаження та запису бази даних у своєму додатку. Я використовую NSOperationі NSOperationQueueдля того ж.

Це сценарій застосування:

  • Отримати всі поштові індекси з місця.
  • Для кожного поштового індексу отримуйте всі будинки.
  • Для кожного будинку принесіть дані мешканця

Як вже було сказано, я визначив NSOperationдля кожного завдання. У першому випадку (Завдання 1) я надсилаю запит на сервер, щоб отримати всі поштові індекси. Делегат всередині NSOperationотримає дані. Потім ці дані записуються в базу даних. Операція з базою даних визначена в іншому класі. З NSOperationкласу я здійснюю виклик функції запису, визначеної в класі бази даних.

Моє питання полягає в тому, чи відбувається запис бази даних в основному потоці чи у фоновому потоці? Оскільки я називав це всередині a, NSOperationя очікував, що він буде працювати в іншому потоці (не MainThread) як NSOperation. Хтось може пояснити цей сценарій, маючи справу з NSOperationта NSOperationQueue.


3
Якщо ви додасте операції до основної черги, то вони будуть виконуватися в основному потоці. Якщо ви створите власну NSOperationQueue і додасте до неї операції, вони будуть виконуватися в потоках цієї черги.
Cy-4AH

1
Я не думаю, що ти отримаєш кращу відповідь, ніж @ Cy-4AH, якщо ти не отримаєш більш конкретного / не надішлеш коду. Я скажу, що ви завжди можете ввести точку зупинки в код, і коли він спрацює, він покаже вам, у якій нитці знаходиться трасування.
Бред Олред

Що означає "Делегат в рамках NSOperation отримає дані". маю на увазі? Не має NSOperationі не NSOperationQueueмістить властивостей делегатів.
Jeffery Thomas

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

Відповіді:


175

Моє питання полягає в тому, чи відбувається запис бази даних в основному потоці чи у фоновому потоці?

Якщо ви створюєте 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дає точніший контроль над тим, що ви хочете зробити. Ви можете створювати залежності між двома операціями (завантажувати та зберігати в базі даних). Для передачі даних між одним блоком та іншим можна, наприклад, припустити, що a 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посилання на Операції, можна знайти тут . Але в двох словах - уникати циклів збереження.


3
відповідь правильна, але зауважте, що правильний рядок коду: NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];і що NSOperationQueue в основному потоці не може бути призупинено.
Gianluca P.

2
@ jacky-boy Не цікаво, чому використання слабких посилань на downloadOperation та saveToDataBaseOperation у кінцевому фрагменті коду?
Водоспад Майкл

RuiAAPeres, привіт, мені цікаво, чому в остаточному фрагменті коду використовуються слабкі посилання? Не могли б ви трохи пролити це світло, будь ласка?
Паван

Ідея @Pavan полягає в тому, що якщо вам трапляється посилання на ту саму операцію блоку всередині власного блоку, ви отримаєте цикл збереження. Для довідки: conradstoll.com/blog/2013/1/19/…
Руй Перес

так який блок такий самий, як і блок, який працює всередині нього?
Паван

16

Якщо ви хочете виконати операцію запису бази даних у фоновому потоці, вам потрібно створити NSManagedObjectContextдля цього потоку.

Ви можете створити фон NSManagedObjectContextу методі запуску відповідного NSOperationпідкласу.

Перевірте в документації Apple паралельність із основними даними.

Ви також можете створити програму, NSManagedObjectContextяка виконує запити у власному фоновому потоці, створюючи її NSPrivateQueueConcurrencyTypeта виконуючи запити всередині свого performBlock:методу.


17
Що змушує вас думати, що це питання пов’язане з основними даними?
Nikolai Ruhe

11

З 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:.


велике спасибі ... u рятуй моє життя: [NSOperationQueue new] // post-iOS4, гарантовано не буде основною темою
ikanimo

9

Потік виконання NSOperation залежить від того, NSOperationQueueкуди ви додали операцію. Зверніть увагу на це твердження у своєму коді -

[[NSOperationQueue mainQueue] addOperation:yourOperation]; // or any other similar add method of NSOperationQueue class

Все це передбачає, що ви не виконували жодних подальших потоків, mainметодом NSOperationяких є справжній монстр, де написані робочі інструкції, які ви написали (очікується).

Однак у випадку одночасних операцій сценарій інший. Черга може породити потік для кожної паралельної операції. Хоча це не гарантується, і це залежить від системних ресурсів проти потреб операційних ресурсів на той момент у системі. Ви можете контролювати паралельність черги операцій за її 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]);

це було не такою основною ниткою. І виявив, що поточний об’єкт потоку не є основним об’єктом потоку.

Отже, спеціальне створення черги в основному потоці (без паралельності між його операціями) не обов'язково означає, що операції будуть виконуватися послідовно в основному потоці.


Я вважаю, що використання [[NSOperationQueue alloc] init]гарантій, що в черзі буде використовуватися базовий фоновий глобальний фон DispatchQueue. Отже, будь-яка власна ініціалізована OperationQueue передаватиме свої завдання у фонові DispatchQueues і, отже, передаватиме завдання у потоки, які не є основним потоком. Щоб подати операції в основний потік, вам потрібно було б отримати основну OperationQueue, використовуючи щось на зразок [NSOperationQueue mainQueue]. Це отримує основну чергу операцій, яка використовує головну DispatchQueue внутрішньо, і, зрештою, передає завдання в основний потік.
Гордоній

1

Короткий зміст документації operations are always executed on a separate thread(пост 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
});

-2

Якщо ви виконуєте будь-які нетривіальні потоки, вам слід використовувати FMDatabaseQueue .


1
У запитанні не згадується конкретна база даних, наприклад fmdb або sqlite.
Микола Рухе

Це правильно, я зробив припущення на основі контексту. Якби додаток підключався до багатокористувацької, безпечної для потоку бази даних, наприклад MySQL, питання не мало б сенсу. Є й інші однокористувацькі бази даних, якими можна користуватися, але SQLite є найпоширенішою на сьогодні.
Холлі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.