Многопоточность в iOS. Введение в GCD, часть 3

В первой части мы узнали, что для выполнения задач существуют очереди. Узнали какие виды очередей существуют и как из создавать. В третьей части мы познакомимся с полезными функциями для управления очередями задач.
Вспомним, что очередь создается вот так:
dispatch_queue_t queue = dispatch_queue_create("com.myapp.myqueue", DISPATCH_QUEUE_CONCURRENT);
Далее мы будем использовать переменную queue, подразумевая что очередь у нас создана.

Остановка и запуск очереди

Чтобы очередь не начинала выполнять новые задачи мы можем использовать функцию dispatch_suspend:
dispatch_suspend(queue);
После вызова этой функции очередь перестанет запускать новые задачи на выполнение. Задачи, которые уже выполняются продолжат выполняться.
Чтобы вернуть очередь к жизни нужно использовать функцию dispatch_resume:
dispatch_resume(queue);

dispatch_barrier_async

Представьте ситуацию, когда в очереди уже выполняется несколько задач. Вам нужно дождаться, когда их выполнение закончится, запустить другую задачу (и только одну!), а затем еще несколько задач. Примером этого могут служить задачи чтения/записи. Сначала вы что-то считываете, затем вам нужно дождаться пока все задачи чтения закончатся, запустить операцию записи, а затем снова чтение.
Для достижения этой цели существует функция dispatch_barrier_async, она ведет себя так же как и dispatch_async (сразу же возвращает управление), но выполняется только когда все задачи в очереди закончились.
Пример использования:
// блок чтения
void(^block_read)() = ^{
    sleep(1+rand()%3);
    NSLog(@"Block read");
    sleep(1+rand()%3);
};
// блок записи void(^block_write)() = ^{ sleep(1+rand()%3); NSLog(@"Block write"); sleep(1+rand()%3); };
// ставим в очередь задачи чтения dispatch_async(queue, block_read); dispatch_async(queue, block_read); dispatch_async(queue, block_read); dispatch_async(queue, block_read);
// ждем пока все задачи завершатся и запускаем block_write dispatch_barrier_async(queue, block_write);
// когда блок записи выполнится - начнут работать задачи чтения dispatch_async(queue, block_read); dispatch_async(queue, block_read); dispatch_async(queue, block_read); dispatch_async(queue, block_read);
Так же существует функция dispatch_barrier_sync, по-сути синхронный аналог dispatch_barrier_async.

Группы задач

Рассмотрим еще один способ дождаться окончания нескольких задач и выполнить после этого еще одну задачу. Добавим условие, что задачи могут быть в разных очередях. В этой ситуации нас выручит «Dispatch group». Мы будем объединять задачи в группу. Для начала создадим группу:
dispatch_group_t group = dispatch_group_create();
Добавить задачу в группу можно функцией dispatch_group_async:
dispatch_group_async(группа, очередь, блок);
Как вы заметили, в эту функцию передаем группу в которую хотим добавить задачу, очередь в которой нужно ее выполнять и блок кода. При вызове этой функции задача ставится на выполнение. Пример:
// какие-либо очереди
dispatch_queue_t queue1 = ...;
dispatch_queue_t queue2 = ...;
dispatch_queue_t queue3 = ...;
// группа dispatch_group_t group = ...;
// добавляем задачи в группу и в разные очереди на выполнение dispatch_group_async(group, queue1, ^{NSLog(@"Hello, World!"); }); dispatch_group_async(group, queue2, ^{NSLog(@"Hello, World!"); }); dispatch_group_async(group, queue3, ^{NSLog(@"Hello, World!"); });
Чтобы узнать, когда все задачи из группы закончатся, мы можем «уведомить» какую-либо очередь, добавив туда задачу. Уведомим главную очередь:
dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"All tasks are done!");});
Когда все задачи из группы выполнятся — в главной очереди будет выполнен блок
^{NSLog(@"All tasks are done!");}

Ожидаем завершения задач

Группы задач так же позволяют просто дождаться окончания выполнения задач из группы либо проверить, выполнились ли они.
После добавления задач в группу и в очередь мы можем вызвать функцию dispatch_group_wait. У нее два аргумента — группа и время таймаута. Когда заканчивается время — функция возвращает выполнение. Можно установить бесконечное время, можно установить нулевое время (тогда будет просто проверено, закончилось ли выполнение задач из группы). Пример:
// какие-либо очереди
dispatch_queue_t queue1 = ...;
dispatch_queue_t queue2 = ...;
dispatch_queue_t queue3 = ...;
// группа dispatch_group_t group = ...;
// добавляем задачи в группу и в разные очереди на выполнение dispatch_group_async(group, queue1, ^{NSLog(@"Hello, World!"); }); dispatch_group_async(group, queue2, ^{NSLog(@"Hello, World!"); }); dispatch_group_async(group, queue3, ^{NSLog(@"Hello, World!"); });
// ждем выполнения всех задач из группы dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
Второй аргумент имеет тип dispatch_time_t (такой же как и при dispatch_after, почитать можно тут). Чтобы просто проверить, выполнились ли задачи:
dispatch_group_wait(group, DISPATCH_TIME_NOW);
В случае, если мы задаем таймаут для ожидания — нам нужно проверить закончилось ли время или все задачи выполнились. Сделать мы можем это так:
// будем ждать 10 секунд
dispatch_time_t waitTime = dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC);
long waitResult = dispatch_group_wait(group, waitTime);
if (waitResult == 0) { // все задачи окончены } else { // таймаут }
Как только группа вам станет не нужна, ее нужно «освободить»:
dispatch_release(group);
Все вышесказанное применимо к Concurrent-очередям. В Serial-очередях таких проблем не возникает.
Мне кажется, что статья получилось немного запутанной. Если считаете что что-то можно упростить — жду комментариев.