Очереди рассылки: как определить, запущены ли они, и как их остановить. - PullRequest
33 голосов
/ 11 октября 2009

Я просто играю с 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;
            });
        });

    }
}

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

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

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

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

Итак: -

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

Ответы [ 3 ]

49 голосов
/ 11 октября 2009

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

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

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

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

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);
});

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

Удачи и веселья!

6 голосов
/ 14 октября 2013

Как узнать, работает ли

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 может быть изменено до желаемого результата.

1 голос
/ 18 июля 2014

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

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