Очереди отправки: как узнать, запущены ли они и как их остановить

Я просто играю с GCD, и я написал игрушку CoinFlipper.

Здесь метод, переворачивающий монеты:

- (void)flipCoins:(NSUInteger)nFlips{

    // Create the queues for work
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL);

    // Split the number of flips into whole chunks of kChunkSize and the remainder.
    NSUInteger numberOfWholeChunks = nFlips / kChunkSize;
    NSUInteger numberOfRemainingFlips = nFlips - numberOfWholeChunks * kChunkSize;

    if (numberOfWholeChunks > 0) {
        for (NSUInteger index = 0; index < numberOfWholeChunks; index++) {
            dispatch_async(queue, ^{
                NSUInteger h = 0;
                NSUInteger t = 0;
                flipTheCoins(kChunkSize, &h, &t);
                dispatch_async(mainQueue, ^{
                    self.nHeads += h;
                    self.nTails += t;
                });
            });
        }
    }
    if (numberOfRemainingFlips > 0) {
        dispatch_async(queue, ^{
            NSUInteger h = 0;
            NSUInteger t = 0;
            flipTheCoins(numberOfRemainingFlips, &h, &t);
            dispatch_async(mainQueue, ^{
                self.nHeads += h;
                self.nTails += t;
            });
        });

    }
}

Как вы можете видеть; Я разбиваю количество переворотов на большие куски, переворачивая их в фоновом режиме и обновляя свойства в основной очереди. Свойства наблюдаются оконным контроллером, и пользовательский интерфейс обновляется с результатами выполнения.

Я просмотрел руководство по программированию Concurrency и документы GCD, и хотя есть способ приостановить очередь, нет способа остановить их и удалить все запущенные и не запущенные объекты.

Я хотел бы иметь возможность подключить кнопку "Стоп", чтобы отменить перелистывание после его начала. С помощью NSOperationQueue я могу узнать свойство operationCount, чтобы узнать, запущен ли он, и cancelAllOperations для удаления блоков в очереди.

Я просмотрел руководство по программированию Concurrency и документы GCD, и хотя есть способ приостановить очередь, нет способа остановить их и удалить все запущенные и не запущенные объекты.

Итак: -

  • Как узнать, все еще ждут ли блоки, добавленные в очередь?
  • Как отменить блоки, которые еще не запущены?
  • Я новичок в материалах GCD, так что я делаю это правильно?

Ответ 1

Это вопрос, который часто возникает при программировании с помощью GCD.

Короткий ответ заключается в том, что GCD не имеет API отмены для очередей. Обоснование:

  • Менеджмент памяти
  • станет значительно более сложным, поскольку данный блок может нести ответственность за бесплатное() при заданном распределении памяти. Всегда управляя блоком, GCD гарантирует, что управление памятью остается легким.
  • Практически невозможно остановить рабочий блок без повреждения.
  • Большинство кодов, требующих логики отмены, уже отслеживают это состояние в частных структурах данных.

Учитывая все эти случаи, гораздо эффективнее и мощнее писать такой код:

dispatch_async(my_obj->queue, ^{
    bool done = false;
    // do_full_update() takes too long, therefore:
    while ( !my_obj->cancelled && !done ) {
        done = do_partial_update(my_obj);
    }
});

О, и чтобы узнать, закончила ли очередь выполнение всех блоков, находящихся в очереди, ваш код может просто выполнить пустой блок с помощью синхронного API:

dispatch_sync(my_obj->queue, ^{});

Как упоминалось в комментариях, лучший способ узнать, когда ваша работа будет выполнена, - использовать группы отправки. Отправляйте все блоки в группу, а затем вы можете добавить обработчик завершения в группу. Как только работа будет завершена, будет завершен блок завершения.

dispatch_group_t myGroup = dispatch_group_create();
dispatch_group_async(myGroup, my_obj->queue, ^{
    bool done = false;
    while ( !my_obj->cancelled && !done ) {
        done = do_partial_update(my_obj);
    }
});
dispatch_group_notify(myGroup, my_obj->queue, ^{
    NSLog(@"Work is done!");
    dispatch_release(myGroup);
});

Как только все ваши блоки будут завершены, группа будет пуста и вызовет блок уведомлений. Оттуда вы можете обновить интерфейс и т.д.

Удачи и получайте удовольствие!

Ответ 2

Как определить, работает ли

BOOL dispatch_queue_is_empty(dispatch_queue_t queue)
{
    dispatch_group_t group = dispatch_group_create();

    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        dispatch_group_leave(group);
    });

    int64_t maxWaitTime = 0.00000005 * NSEC_PER_SEC;
    BOOL isReady = dispatch_group_wait(group, maxWaitTime) == 0;

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
        dispatch_release(group);
    });

    return isReady;
}

Проверить в приложении

dispatch_queue_t queue = dispatch_queue_create("test", 0);

NSLog(@"Is empty %@", dispatch_queue_is_empty(queue) ? @"YES" : @"NO");

dispatch_async(queue, ^{
    for(int i = 0; i < 100; i++)
    {
        NSLog(@"... %i", i);
    }
});

NSLog(@"Is empty %@", dispatch_queue_is_empty(queue) ? @"YES" : @"NO");

Результат

Is empty YES
Is empty NO
... 0
... 1
... 2
... 3
... 4
... 5
... 6
... 7
... 8
... 9

Значение по умолчанию для переменной maxWaitTime может быть изменено до желаемого результата.

Ответ 3

Если у вас есть очередная диспетчерская очередь или параллельная очередь отправки, вот код, который может сделать то же самое.

BOOL __block queueIsEmpty = false;
dispatch_barrier_async (_dispatchQueue, ^{
    queueIsEmpty = true;
});

while (!queueIsEmpty) {
    int i = 0;  // NOOP instruction
}

// At this point your queue should be empty.