Имеет ли 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. Все эти три операции взаимосвязаны друг с другом.


РЕДАКТИРОВАТЬ: Извините, ребята. Я пропустил строку в приведенном выше коде. Мой код выглядит так.

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

Еще раз прошу прощения за недоразумение.


person EmptyStack    schedule 05.07.2013    source источник
comment
Возможно, dispatch_sync может быть здесь полезен ...   -  person Dave DeLong    schedule 05.07.2013
comment
@DaveDeLong Возможно. Я видел, как разработчики использовали dispatch_sync, потому что это кажется логичным, но на самом деле часто в этом нет необходимости. EmptyStack не рассказал, почему он хочет подождать, поэтому мы не можем посоветовать, хорошая это идея или нет.   -  person Rob    schedule 05.07.2013
comment
@Rob ага, dispatch_sync может быть очень опасным, потому что таким образом гораздо проще зайти в тупик. Но иногда это правильно. :)   -  person Dave DeLong    schedule 05.07.2013
comment
ДэйвДелонг и Роб, спасибо, ребята. На самом деле я пропустил несколько строк в приведенном выше коде. Прости за это. Я отредактировал свой вопрос. Пожалуйста, проверьте. Спасибо.   -  person EmptyStack    schedule 06.07.2013


Ответы (7)


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

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

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

person David Rönnqvist    schedule 05.07.2013
comment
Прохладный. Спасибо за объяснение. Вы имеете в виду, что все будет нормально, если я изменю все dispatch_async (dispatch_get_main_queue () на dispatch_sync (dispatch_get_main_queue ()? - person EmptyStack; 05.07.2013
comment
Да, это сделает их синхронными (ожидающими). - person David Rönnqvist; 05.07.2013
comment
Вы сказали, что обновления пользовательского интерфейса должны быть быстрыми. Но моя настоящая проблема в том, что обновления пользовательского интерфейса действительно отнимают много времени. Что я должен делать? Спасибо. - person EmptyStack; 06.07.2013
comment
@EmptyStack Что вы на самом деле делаете в этих обновлениях пользовательского интерфейса? Это все обновления пользовательского интерфейса или какие-то вычисления пользовательского интерфейса? Перерисовываются ли части пользовательского интерфейса с помощью drawRect:? Если да, то можно ли куда-нибудь позвонить setNeedsDisplayInRect: вместо setNeedsDisplay? - person David Rönnqvist; 06.07.2013

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

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.

person StatusReport    schedule 05.07.2013
comment
Прохладный. Спасибо за Ваш ответ! - person EmptyStack; 05.07.2013

Предложенный вами 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, когда пользователь закрывает сцену или снова вызывает метод, тогда я мог бы быть склонен использовать очередь операций, которая предлагает возможности отмены. Это полностью зависит от того, чего вы пытаетесь достичь.

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

person Rob    schedule 05.07.2013
comment
Ой. На самом деле мой исходный код выглядит так, как вы разместили выше. Я пропустил это в своем вопросе. Извините. Я обновил свой вопрос. Спасибо за ОТЛИЧНЫЕ ПРЕДЛОЖЕНИЯ. - person EmptyStack; 06.07.2013

Нет, не дождется.

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

person Wain    schedule 05.07.2013
comment
Но для таких вещей, как обновления пользовательского интерфейса, вы не хотите ждать. Если вы хотите подождать, просто добавьте отправку в фоновый поток в качестве последних операторов кода, который вы отправляете в основной поток. - person gnasher729; 19.02.2014
comment
что делает этот материал GCD. Так что написание депеши чище и лучше. - person Karsten; 28.02.2017

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

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) ( т.е. дисковый ввод-вывод регулируется, а приоритет планирования потока устанавливается на самое низкое значение).

person Ashokios    schedule 08.12.2014

Хорошая практика: Группы отправки

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
});
person ChavirA    schedule 12.12.2018

Хорошо, есть два способа сделать это:

// 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");
       });  
   });
}
person kokemomuke    schedule 05.03.2014
comment
это не работает в фоновом режиме .. вы можете дать мне то, что может работать в фоновом режиме - person Jignesh B; 01.08.2014
comment
я вас не понял, просто уберите сон из основного потока. - person kokemomuke; 07.08.2014