Требуется разъяснение поведения dispatch_group_wait(), когда dispatch_group_create() и dispatch_group_enter() вызываются из разных очередей.

Я просматриваю учебник Рэя Вендерлиха по использованию очередей отправки, чтобы получать уведомления о завершении группы задач. http://www.raywenderlich.com/63338/grand-central-dispatch-in-depth-part-2

Первый код, показанный в разделе «Код, который работает», взят прямо из учебника. Представление «Предупреждение» (финальный блок завершения) выполняется после завершения всех 3 загрузок.

Я попытался поиграть с этим и переместил асинхронную отправку вниз в «Код, который не работает», чтобы посмотреть, что произойдет, если dispatch_group_create() и dispatch_group_enter() произойдут в разных очередях. В этом случае, dispatch_group_enter(), похоже, не регистрируется, потому что dispatch_group_wait() немедленно завершается, а представление предупреждения (окончательный блок завершения) выполняется еще до завершения всех загрузок.

Кто-нибудь может объяснить, что происходит во втором случае? (Это просто для моего понимания того, как работает диспетчерская группа, и я понимаю, что лучше поместить всю функцию в глобальную параллельную очередь, чтобы избежать блокировки основного потока).

Код, который работает

 - (void)downloadPhotosWithCompletionBlock:(BatchPhotoDownloadingCompletionBlock)completionBlock
{

 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),^{

    __block NSError *error;
    dispatch_group_t downloadGroup = dispatch_group_create();

    for (NSInteger i = 0; i < 3; i++)
    {
        NSURL *url;
        switch (i) {
            case 0:
                url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString];
                break;
            case 1:
                url = [NSURL URLWithString:kSuccessKidURLString];
                break;
            case 2:
                url = [NSURL URLWithString:kLotsOfFacesURLString];
                break;
            default:
                break;
        }


            dispatch_group_enter(downloadGroup);
            __block Photo *photo = [[Photo alloc] initwithURL:url
                                  withCompletionBlock:^(UIImage *image, NSError *_error) {
                                      if (_error) {
                                          error = _error;
                                      }
                                      NSLog(@"Finished completion block for photo alloc for URL %@ and photo is %@",url,photo) ;
                                      dispatch_group_leave(downloadGroup);
                                  }];

            [[PhotoManager sharedManager] addPhoto:photo];
            NSLog(@"Finished adding photo to shared manager for URL %@ and photo is %@",url,photo) ;
    }

    dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER); // 5
    dispatch_async(dispatch_get_main_queue(), ^{
        if (completionBlock) {
            NSLog(@"Executing completion block after download group complete") ;
            completionBlock(error);
        }
    }) ;
  }) ;
}

ОТРЕДАКТИРОВАНО Код, который не работает с дополнительными операторами NSLog

Код, который не работает

 - (void)downloadPhotosWithCompletionBlock:(BatchPhotoDownloadingCompletionBlock)completionBlock
 {

__block NSError *error;
dispatch_group_t downloadGroup = dispatch_group_create();

for (NSInteger i = 0; i < 3; i++)
{
    NSURL *url;
    switch (i) {
        case 0:
            url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString];
            break;
        case 1:
            url = [NSURL URLWithString:kSuccessKidURLString];
            break;
        case 2:
            url = [NSURL URLWithString:kLotsOfFacesURLString];
            break;
        default:
            break;
    }

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),^{
        dispatch_group_enter(downloadGroup);
        NSLog(@"Enetered group for URL %@",url) ;
        __block Photo *photo = [[Photo alloc] initwithURL:url
                                      withCompletionBlock:^(UIImage *image, NSError *_error) {
                                          if (_error) {
                                              error = _error;
                                          }
                                          NSLog(@"Finished completion block for photo alloc for URL %@ and photo is %@",url,photo) ;
                                          dispatch_group_leave(downloadGroup);
                                      }];

        [[PhotoManager sharedManager] addPhoto:photo];
        NSLog(@"Finished adding photo to shared manager for URL %@ and photo is %@",url,photo) ;
    }) ;
}

NSLog(@"Executing wait statement") ;
dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER); // 5
dispatch_async(dispatch_get_main_queue(), ^{
    if (completionBlock) {
        NSLog(@"Executing completion block after download group complete") ;
        completionBlock(error);
    }
}) ;
}

person Smart Home    schedule 22.02.2016    source источник


Ответы (1)


«dispatch_group_enter(), похоже, не регистрируется», потому что к тому времени, когда вызывается dispatch_group_wait(), он еще не был вызван. Или, скорее, не гарантируется, что он был вызван. Есть состояние гонки.

Это не конкретно о разных очередях. Речь идет о параллелизме и асинхронности.

dispatch_async() просто означает «добавить задачу в список» с неявным пониманием того, что что-то, где-то, когда-нибудь возьмет задачи из этого списка и выполнит их. Он возвращается к вызывающей стороне сразу же после того, как задача была помещена в список. Он не ждет начала выполнения задачи, не говоря уже о ее завершении.

Таким образом, ваш цикл for выполняется очень быстро, и к тому времени, когда он завершится, может случиться так, что ни одна из задач, поставленных в очередь, еще не запущена. Или, если кто-то начал, возможно, они не закончили вхождение в группу.

Ваш код может завершить вызов dispatch_group_wait() до того, как что-либо войдет в группу.

Обычно вы хотите убедиться, что все соответствующие вызовы dispatch_group_enter() завершены до того, как будет сделан вызов dispatch_group_wait(). Самый простой способ сделать это — заставить их все выполняться синхронно в одном контексте выполнения. То есть не помещайте вызовы dispatch_group_enter() внутрь блоков, которые отправляются асинхронно.

person Ken Thomases    schedule 22.02.2016
comment
Спасибо, это имеет смысл. Пожалуйста, смотрите редактирование выше. Я вижу, что инструкция NSLog после group_ enter выполняется до инструкции NSLog до dispatch_wait. Кажется, это указывает на какую-то основную причину, отличную от вашего объяснения выше. Должен ли я отлаживать с помощью точек останова? Выходные данные NSLog в Xcode: 2016-02-21 21:37:01.155 GooglyPuff[393:136916] Это ваш внешний голос: я печатал до или после внутреннего голоса? 2016-02-21 21:37:05.319 GooglyPuff[393:136937] Введена группа для URL-адреса i.imgur. com/UvqEgCv.png 2016-02-21 21:37:05.319 GooglyPuff[393:136916] Выполнение оператора ожидания - person Smart Home; 22.02.2016
comment
Просто выполняется с точками останова. Сначала выполняется 1-й диспетчер_группы_энтер, затем диспетчер_группы_ожидания, а затем оставшиеся два диспетчера_группы_энтер. Есть ли задержка между выполнением dispatch_group_enter и отражением изменения состояния во время вызова dispatch_group_wait? Добавление наименьшей задержки ([NSThread sleepForTimeInterval:0.001];) устраняет состояние гонки, и все происходит в ожидаемом порядке. - person Smart Home; 22.02.2016
comment
Поместите в свой вопрос некоторый вывод из прогона без точек останова. Я не понимаю, что вы написали в комментарии выше. Добавление задержек может уменьшить окно для состояния гонки, но не обязательно устраняет его. Состояние гонки — это вопрос логики программы, а не просто капризы выполнения (например, занята ли система, икает ли планировщик или что-то еще). - person Ken Thomases; 22.02.2016
comment
Я имел в виду следующее: когда я запускал код с двумя точками останова: одной в строке dispatch_group_enter() и одной в строке dispatch_group_wait(), я вижу, что dispatch_group_enter() в первой итерации цикла for достигается первым. Поскольку dispatch_group_enter() выполняется первым, почему dispatch_group_wait() не приводит к ожиданию? есть ли какая-то задержка между выполнением group_enter() и отражением изменения его состояния? Кроме того, куда вы поместили вывод, упомянутый выше. Поместите в свой вопрос вывод из прогона без точек останова. я не вижу этого в своем вопросе - person Smart Home; 22.02.2016
comment
Я просил вас поместить вывод в редактирование вопроса. Тяжело читать комментарий. Кроме того, есть две проблемы с точками останова: 1) они по своей природе изменяют время выполнения кода; и 2) если вы прервете строку с dispatch_group_enter(), вы остановитесь до ее вызова, а не после. Конечно, для фактического выполнения требуется некоторое время (даже если оно очень мало). - person Ken Thomases; 22.02.2016