Многопоточность в 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-очередях таких проблем не возникает.

Мне кажется, что статья получилось немного запутанной. Если считаете что что-то можно упростить — жду комментариев.

Все части:

  • Часть 1
  • Часть 2
  • Часть 4
Рейтинг
( Пока оценок нет )
webnewsite.ru / автор статьи
Загрузка ...

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: