Does dispatch_async (dispatch_get_main_queue(), ^ {...}); подождать до конца?

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

- (void)doCalculationsAndUpdateUIs {

    // DATA PROCESSING 1
    // UI UPDATE 1

    // DATA PROCESSING 2
    // UI UPDATE 2

    // DATA PROCESSING 3
    // UI UPDATE 3
} 

Поскольку требуется много времени, я хотел выполнить обработку данных в фоновом потоке, используя

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{

Но поскольку и обработка данных, и обновления пользовательского интерфейса находятся в одном и том же методе, я хотел переместить только обновления пользовательского интерфейса в основном потоке, используя

dispatch_async(dispatch_get_main_queue(), ^{

Наконец, мой метод выглядит так:

- (void)doCalculationsAndUpdateUIs {

    // DATA PROCESSING 1 
    dispatch_async(dispatch_get_main_queue(), ^{
        // UI UPDATE 1
    });

    /* I expect the control to come here after UI UPDATE 1 */

    // DATA PROCESSING 2
    dispatch_async(dispatch_get_main_queue(), ^{
        // UI UPDATE 2
    });

    /* I expect the control to come here after UI UPDATE 2 */

    // DATA PROCESSING 3
    dispatch_async(dispatch_get_main_queue(), ^{
        // UI UPDATE 3
    });
}

Это действительно работает? Это действительно хорошая практика? Каков наилучший способ достичь этого?

P.S. Все эти три операции взаимосвязаны друг с другом.


EDIT: Извините, ребята. У меня пропущена строка в приведенном выше коде. Мой фактический код выглядит так.

- (void)doCalculationsAndUpdateUIs {

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        // DATA PROCESSING 1 
        dispatch_async(dispatch_get_main_queue(), ^{
            // UI UPDATE 1
        });

        /* I expect the control to come here after UI UPDATE 1 */

        // DATA PROCESSING 2
        dispatch_async(dispatch_get_main_queue(), ^{
            // UI UPDATE 2
        });

        /* I expect the control to come here after UI UPDATE 2 */

        // DATA PROCESSING 3
        dispatch_async(dispatch_get_main_queue(), ^{
            // UI UPDATE 3
        });
    });
}

Я снова извиняюсь за путаницу.

Ответ 1

Нет, это не ждет, и то, как вы делаете это в этом примере, не является хорошей практикой.

dispatch_async всегда асинхронный. Это просто, что вы вставляете все блоки пользовательского интерфейса в одну очередь, чтобы разные блоки выполнялись последовательно, но параллельно с кодом обработки данных.

Если вы хотите, чтобы обновление ожидало, вы можете использовать dispatch_sync вместо этого.

// This will wait to finish
dispatch_sync(dispatch_get_main_queue(), ^{
    // Update the UI on the main thread.
});

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

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // Background work

    dispatch_async(dispatch_get_main_queue(), ^{
        // Update UI

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            // Background work

            dispatch_async(dispatch_get_main_queue(), ^{
                // Update UI
            });
        });
    });
});

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

Ответ 2

Вы должны отправить свою основную очередь в блок, в котором выполняется вычисление. Например (здесь я создаю очередь отправки и не использую глобальную):

dispatch_queue_t queue = dispatch_queue_create("com.example.MyQueue", NULL);
dispatch_async(queue, ^{
  // Do some computation here.

  // Update UI after computation.
  dispatch_async(dispatch_get_main_queue(), ^{
    // Update the UI on the main thread.
  });
});

Конечно, если вы создаете очередь, не забывайте dispatch_release, если вы ориентируетесь на версию iOS до 6.0.

Ответ 3

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

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

Таким образом, это может выглядеть так:

- (void)doCalculationsAndUpdateUIs {

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{

        // DATA PROCESSING 1 

        dispatch_async(dispatch_get_main_queue(), ^{
            // UI UPDATION 1
        });

        /* I expect the control to come here after UI UPDATION 1 */

        // DATA PROCESSING 2

        dispatch_async(dispatch_get_main_queue(), ^{
            // UI UPDATION 2
        });

        /* I expect the control to come here after UI UPDATION 2 */

        // DATA PROCESSING 3

        dispatch_async(dispatch_get_main_queue(), ^{
            // UI UPDATION 3
        });
    });
}

С точки зрения того, как вы отправляете обновления своего пользовательского интерфейса асинхронно с помощью dispatch_async (где фоновый процесс не будет ждать обновления пользовательского интерфейса) или синхронно с dispatch_sync (там, где он будет ожидать обновления пользовательского интерфейса), возникает вопрос: почему вы хотите сделать это синхронно: действительно ли хотите замедлить фоновый процесс, поскольку он ждет обновления пользовательского интерфейса, или вы хотите, чтобы фоновый процесс продолжался, когда происходит обновление пользовательского интерфейса.

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


В ответ на ваш вопрос о том, является ли это "наилучшим способом достижения этого", нам трудно сказать, не зная больше о решаемой бизнес-проблеме. Например, если вы можете называть это doCalculationsAndUpdateUIs несколько раз, я мог бы склоняться к использованию собственной очереди, а не к параллельной глобальной очереди, чтобы гарантировать, что они не перешагнут друг на друга. Или, если вам может потребоваться отменить этот doCalculationsAndUpdateUIs, когда пользователь увольняет сцену или снова вызовет этот метод, я могу склоняться к использованию очереди операций, которая предлагает возможности отмены. Это зависит полностью от того, чего вы пытаетесь достичь.

Но, в общем, характер асинхронной отправки сложной задачи в фоновую очередь, а затем асинхронную отправку обновления пользовательского интерфейса обратно в основную очередь очень распространен.

Ответ 4

Если вы хотите запустить отдельную операцию с независимой очередью и не связаны с другими параллельными операциями, вы можете использовать глобальную параллельную очередь:

dispatch_queue_t globalConcurrentQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

Это вернет параллельную очередь с заданным приоритетом, как указано в документации:

DISPATCH_QUEUE_PRIORITY_HIGH Элементы, отправленные в очередь, будут выполняться с высоким приоритетом, то есть очередь будет запланирована для выполнения перед любой очередью приоритетов по умолчанию или с низким приоритетом.

DISPATCH_QUEUE_PRIORITY_DEFAULT Элементы, отправленные в очередь, будут выполняться с приоритетом по умолчанию, то есть очередь будет запланирована для выполнения после того, как запланированы все очереди с высоким приоритетом, но до того, как запланированы очередные очереди с низким приоритетом.

DISPATCH_QUEUE_PRIORITY_LOW Элементы, отправленные в очередь, будут выполняться с низким приоритетом, т.е. очередь будет запланирована для выполнения после того, как запланированы все приоритеты по умолчанию и очереди с высоким приоритетом.

DISPATCH_QUEUE_PRIORITY_BACKGROUND Элементы, отправленные в очередь, будут выполняться с приоритетом фона, то есть очередь будет запланирована для выполнения после того, как все очереди с более высоким приоритетом будут запланированы, и система будет запускать элементы в этой очереди в потоке с фоновым статусом согласно setpriority ( 2) (т.е. дисковый ввод-вывод дросселируется, а приоритет планирования потоков устанавливается на минимальное значение).

Ответ 5

Нет, он не будет ждать.

Вы можете использовать performSelectorOnMainThread:withObject:waitUntilDone:.

Ответ 6

dispatch_queue_t queue = dispatch_queue_create("com.example.MyQueue", NULL);
dispatch_async(queue, ^{
  // Do some computation here.

  // Update UI after computation.
  dispatch_async(dispatch_get_main_queue(), ^{
    // Update the UI on the main thread.
  });
});

Ответ 7

Хорошей практикой является: рассылка групп

dispatch_group_t imageGroup = dispatch_group_create();

dispatch_group_enter(imageGroup);
[uploadImage executeWithCompletion:^(NSURL *result, NSError* error){
    // Image successfully uploaded to S3
    dispatch_group_leave(imageGroup);
}];

dispatch_group_enter(imageGroup);
[setImage executeWithCompletion:^(NSURL *result, NSError* error){
    // Image url updated
    dispatch_group_leave(imageGroup);
}];

dispatch_group_notify(imageGroup,dispatch_get_main_queue(),^{
    // We get here when both tasks are completed
});

Ответ 8

ОК, есть два способа сделать это:

// GLOBAL_CONCURRENT_QUEUE


- (void)doCalculationsAndUpdateUIsWith_GlobalQUEUE 
{
    dispatch_queue_t globalConcurrentQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(globalConcurrentQ, ^{

       // DATA PROCESSING 1
       sleep(1);
       NSLog(@"Hello world chekpoint 1");
       dispatch_sync(dispatch_get_main_queue(), ^{
           // UI UPDATION 1
           sleep(1);
           NSLog(@"Hello world chekpoint 2");
       });

        /* the control to come here after UI UPDATION 1 */
        sleep(1);
        NSLog(@"Hello world chekpoint 3");
        // DATA PROCESSING 2

        dispatch_sync(dispatch_get_main_queue(), ^{
            // UI UPDATION 2
            sleep(1);
            NSLog(@"Hello world chekpoint 4");
        });

        /* the control to come here after UI UPDATION 2 */
        sleep(1);
        NSLog(@"Hello world chekpoint 5");
        // DATA PROCESSING 3

        dispatch_sync(dispatch_get_main_queue(), ^{
            // UI UPDATION 3
            sleep(1);
            NSLog(@"Hello world chekpoint 6");
        });
   });
}



// SERIAL QUEUE
- (void)doCalculationsAndUpdateUIsWith_GlobalQUEUE 
{

    dispatch_queue_t serialQ = dispatch_queue_create("com.example.MyQueue", NULL);
    dispatch_async(serialQ, ^{

       // DATA PROCESSING 1
       sleep(1);
       NSLog(@"Hello world chekpoint 1");

       dispatch_sync(dispatch_get_main_queue(), ^{
           // UI UPDATION 1
           sleep(1);
           NSLog(@"Hello world chekpoint 2");
       });


       sleep(1);
       NSLog(@"Hello world chekpoint 3");
       // DATA PROCESSING 2

       dispatch_sync(dispatch_get_main_queue(), ^{
           // UI UPDATION 2
           sleep(1);
           NSLog(@"Hello world chekpoint 4");
       });  
   });
}