Хто-небудь може пояснити справді чіткими випадками використання, для чого призначена dispatch_sync
in GCD
? Я не можу зрозуміти, де і чому мені довелося б це використовувати.
Дякую!
Хто-небудь може пояснити справді чіткими випадками використання, для чого призначена dispatch_sync
in GCD
? Я не можу зрозуміти, де і чому мені довелося б це використовувати.
Дякую!
Відповіді:
Ви використовуєте його, коли хочете виконати блок і чекати результатів.
Одним із прикладів цього є шаблон, коли ви використовуєте чергу відправлення замість блокування для синхронізації. Наприклад, припустимо, що у вас є загальний NSMutableArray a
, доступ якого опосередкований чергою відправлення q
. Фоновий потік може додаватися до масиву (асинхронно), тоді як ваш потік переднього плану витягує перший елемент (синхронно):
NSMutableArray *a = [[NSMutableArray alloc] init];
// All access to `a` is via this dispatch queue!
dispatch_queue_t q = dispatch_queue_create("com.foo.samplequeue", NULL);
dispatch_async(q, ^{ [a addObject:something]; }); // append to array, non-blocking
__block Something *first = nil; // "__block" to make results from block available
dispatch_sync(q, ^{ // note that these 3 statements...
if ([a count] > 0) { // ...are all executed together...
first = [a objectAtIndex:0]; // ...as part of a single block...
[a removeObjectAtIndex:0]; // ...to ensure consistent results
}
});
-performSelector:onThread:withObject:waitUntilDone:
або performSelectorOnMainThread:withObject:waitUntilDone:
встановлення waitUntilDone
ТАК.
Спочатку зрозумій свого брата dispatch_async
//Do something
dispatch_async(queue, ^{
//Do something else
});
//Do More Stuff
Ви використовуєте dispatch_async
для створення нового потоку. Коли ви це зробите, поточний потік не зупиниться. Цей засіб //Do More Stuff
може бути виконаний раніше//Do something else
фінішу
Що станеться, якщо ви хочете, щоб поточний потік зупинився?
Ви взагалі не використовуєте відправлення. Просто напишіть код нормально
//Do something
//Do something else
//Do More Stuff
Тепер скажіть, що хочете зробити щось за РІЗНОЮ ниткою, і все ж зачекайте, як ніби, і переконайтесь, що матеріали виконуються послідовно .
Є багато причин для цього. Наприклад, оновлення інтерфейсу здійснюється в основному потоці.
Ось де ви використовуєте dispatch_sync
//Do something
dispatch_sync(queue, ^{
//Do something else
});
//Do More Stuff
Тут ви зробили //Do something
//Do something else
і //Do More stuff
зробили послідовно, хоча//Do something else
це робиться з іншої нитки.
Зазвичай, коли люди використовують різні нитки, вся мета полягає в тому, щоб щось можна було виконати, не чекаючи. Скажімо, ви хочете завантажити велику кількість даних, але хочете, щоб інтерфейс працював гладко.
Отже, dispatch_sync використовується рідко. Але воно там. Я особисто ніколи цим не користувався. Чому б не попросити якийсь зразок коду чи проекту, який використовує dispatch_sync.
dispatch_sync
є інший процес асинхронізації для використання як зворотний виклик. Наприклад, performBlock
метод NSManagedObjectContext Core Data може використовувати його в кінці блоку як зворотний виклик.
dispatch_sync семантично еквівалентний традиційному блокуванню мьютексу.
dispatch_sync(queue, ^{
//access shared resource
});
працює так само, як
pthread_mutex_lock(&lock);
//access shared resource
pthread_mutex_unlock(&lock);
Девід Гелхар не сказав, що його приклад буде працювати лише тому, що він тихо створив послідовну чергу (передав NULL у dispatch_queue_create, що дорівнює DISPATCH_QUEUE_SERIAL).
Якщо ви хочете створити паралельну чергу (щоб отримати всю багатопотокову потужність), його код призведе до аварії через мутацію NSArray (addObject :) під час мутації (removeObjectAtIndex :) або навіть поганий доступ (діапазон NSArray за межами). У цьому випадку ми повинні використовувати бар’єр, щоб забезпечити ексклюзивний доступ до NSArray, поки обидва блоки працюють. Він не тільки виключає всі інші записи в NSArray під час роботи, але також виключає всі інші читання, роблячи модифікацію безпечною.
Приклад для одночасної черги повинен виглядати так:
NSMutableArray *a = [[NSMutableArray alloc] init];
// All access to `a` is via this concurrent dispatch queue!
dispatch_queue_t q = dispatch_queue_create("com.foo.samplequeue", DISPATCH_QUEUE_CONCURRENT);
// append to array concurrently but safely and don't wait for block completion
dispatch_barrier_async(q, ^{ [a addObject:something]; });
__block Something *first = nil;
// pop 'Something first' from array concurrently and safely but wait for block completion...
dispatch_barrier_sync(q, ^{
if ([a count] > 0) {
first = [a objectAtIndex:0];
[a removeObjectAtIndex:0];
}
});
// ... then here you get your 'first = [a objectAtIndex:0];' due to synchronised dispatch.
// If you use async instead of sync here, then first will be nil.
Якщо вам потрібні деякі зразки практичного використання, подивіться на це моє питання:
Як вирішити цю тупикову ситуацію, яка трапляється випадково?
Я вирішую це шляхом забезпечення того, щоб мій головний managedObjectContext створювався в основному потоці. Процес дуже швидкий, і я не проти зачекати. Не чекати означає, що мені доведеться мати справу з великою кількістю питань безпеки.
Мені потрібна dispatch_sync, оскільки для основного потоку потрібно виконати певний код, який відрізняється від потоку, де виконується кодування.
Отже, якщо ви хочете, щоб код 1. 1. Продовжуйте, як зазвичай. Ви не хочете турбуватися про умови перегонів. Ви хочете переконатися, що код заповнений перед тим, як рухатись далі. 2. Виконано на іншій нитці
використовувати dispatch_sync.
Якщо 1 порушено, використовуйте dispatch_async. Якщо 2 порушено, просто напишіть код, як зазвичай.
Поки що я роблю це лише один раз, а саме тоді, коли потрібно щось робити з основною ниткою.
Отже, ось код:
+(NSManagedObjectContext *)managedObjectContext {
NSThread *thread = [NSThread currentThread];
//BadgerNewAppDelegate *delegate = [BNUtilitiesQuick appDelegate];
//NSManagedObjectContext *moc = delegate.managedObjectContext;
if ([thread isMainThread]) {
//NSManagedObjectContext *moc = [self managedObjectContextMainThread];
return [self managedObjectContextMainThread];
}
else{
dispatch_sync(dispatch_get_main_queue(),^{
[self managedObjectContextMainThread];//Access it once to make sure it's there
});
}
// a key to cache the context for the given thread
NSMutableDictionary *managedObjectContexts =[self thread].managedObjectContexts;
@synchronized(self)
{
if ([managedObjectContexts objectForKey:[self threadKey]] == nil ) {
NSManagedObjectContext *threadContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
threadContext.parentContext = [self managedObjectContextMainThread];
//threadContext.persistentStoreCoordinator= [self persistentStoreCoordinator]; //moc.persistentStoreCoordinator;// [moc persistentStoreCoordinator];
threadContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
[managedObjectContexts setObject:threadContext forKey:[self threadKey]];
}
}
return [managedObjectContexts objectForKey:[self threadKey]];
}
dispatch_sync в основному використовується всередині блоку dispatch_async для виконання деяких операцій з основним потоком (наприклад, оновлення ui).
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Update UI in main thread
dispatch_sync(dispatch_get_main_queue(), ^{
self.view.backgroundColor = color;
});
});
Ось реалістичний приклад на півдорозі. У вас 2000 zip-файлів, які ви хочете аналізувати паралельно. Але бібліотека zip не захищена від потоків. Тому вся робота, яка стосується zip-бібліотеки, надходить у unzipQueue
чергу. (Приклад наведено в Ruby, але всі виклики відображаються безпосередньо в бібліотеці C. "apply", наприклад, map to dispatch_apply (3) )
#!/usr/bin/env macruby -w
require 'rubygems'
require 'zip/zipfilesystem'
@unzipQueue = Dispatch::Queue.new('ch.unibe.niko.unzipQueue')
def extractFile(n)
@unzipQueue.sync do
Zip::ZipFile.open("Quelltext.zip") { |zipfile|
sourceCode = zipfile.file.read("graph.php")
}
end
end
Dispatch::Queue.concurrent.apply(2000) do |i|
puts i if i % 200 == 0
extractFile(i)
end
Я використовував диспетчерську синхронізацію, коли знаходився в асинхронній диспетчері, щоб сигналізувати про зміну інтерфейсу користувача в основний потік.
Мій асинхронний блок стримує лише трохи, і я знаю, що основний потік знає про зміни інтерфейсу та буде діяти над ними. Як правило, це використовується в блоці обробки коду, який займає певний час процесора, але я все одно хочу вжити змін в інтерфейсі всередині цього блоку. Діяти зміни інтерфейсу користувача в асинхронному блоці марно, оскільки інтерфейс, на мою думку, працює на основному потоці. Також дія з ними як вторинних асинхронних блоків або самостійного делегування призводить до того, що користувальницький інтерфейс бачить їх лише через кілька секунд, і це виглядає запізнено.
Приклад блоку:
dispatch_queue_t myQueue = dispatch_queue_create("my.dispatch.q", 0);
dispatch_async(myQueue,
^{
// Do some nasty CPU intensive processing, load file whatever
if (somecondition in the nasty CPU processing stuff)
{
// Do stuff
dispatch_sync(dispatch_get_main_queue(),^{/* Do Stuff that affects UI Here */});
}
});
dispatch_async
за яким слідує adispatch_sync
в одній черзі. Однак цей самий шаблон корисний, коли ви хочете породити кілька одночасних завдань в іншій черзі, а потім чекати їх усіх.