Является ли цикл выполнения синхронным или асинхронным?

Я провожу эксперимент на основе этого ответа: https://stackoverflow.com/a/17921058/767653

Это мой минимальный воспроизводимый код:


- (void)viewDidLoad {
  [super viewDidLoad];
  
  __block BOOL completed = NO;
  [self doMyWork:^{
    completed = YES;
  }];
  
  while (!completed) {
    [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
  }
}

Насколько я понимаю, этот код зашел в тупик. Поскольку блок завершения запускается в основном потоке, для того, чтобы блок завершения был запущен, viewDidLoad должен вернуться. И чтобы viewDidLoad вернулся, необходимо запустить блок завершения для переключения флага completed (следовательно, циклическое ожидание).

Однако, когда я запускаю его, в этом коде нет тупика. Где я неправильно понял?

Вы не заблокировали основную тему, потому что ни разу не вызывали блокировку wait. RunUntilDate предотвратит дальнейшее выполнение внутри viewDidLoad, но основной цикл выполнения все еще обрабатывается. Вы наверняка сделали пользовательский интерфейс своего приложения не отвечающим на запросы в течение трех секунд. Для возникновения взаимоблокировки вам нужны три вещи: взаимное исключение, отсутствие приоритета и удержание и ожидание. В этом случае у вас нет «удержания и ожидания».

Paulw11 15.05.2024 04:58

Как можно приостановить выполнение viewDidLoad, находясь в середине? Я думал, вам нужно выполнить эту функцию, чтобы основная очередь могла получить следующую задачу. Или я неправильно понимаю? И что я сделал иначе, чем по ссылке, которую предоставил?

OMGPOP 15.05.2024 05:48

Замените [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; на Thread.sleep(1) и посмотрите, что произойдет. В этом разница между блокировкой потока и задержкой возврата viewDidLoad, но при этом выполнением RunLoop. Ваш текущий код заблокировал обновления пользовательского интерфейса, и это плохая идея, но сам поток не заблокирован, поскольку вы передали выполнение обратно в RunLoop.

Paulw11 15.05.2024 05:51

У меня могут возникнуть проблемы с пониманием «перенес выполнение обратно в цикл выполнения». Можно ли приостановить выполнение функции (а затем выполнить другую функцию) без переключения потока? У меня всегда сложилось впечатление, что выполнение функции можно приостановить при переключении потока (так называемое переключение контекста потока), но я не знал, что это также возможно в пределах одного потока. это удивительно.

OMGPOP 15.05.2024 05:58

если вы позвоните someOtherFunction() в viewDidLoad, выполнение будет приостановлено? Нет, вы перенесли его на someOtherFunction. В вашем случае вы передали его в runUntilDate, и эта функция продолжит выполнять задачи в цикле выполнения.

Paulw11 15.05.2024 06:20

В каждом потоке есть только один стек вызовов. После вызова someOtherFunction запись активации (или «кадр») viewDidLoad все еще находится в стеке вызовов, а новая запись для someOtherFrame просто располагается поверх нее. В моем примере это возможно только в том случае, если runUntilDate фактически вызывает мой блок завершения. Я правильно понимаю?

OMGPOP 15.05.2024 21:06

Косвенно, да. Ваш блок завершения находится в очереди таймера цикла выполнения. Поскольку вы поддерживаете RunLoop в рабочем состоянии, он в конечном итоге вызовет ваш обработчик завершения. Как я уже сказал, вместо этого используйте Thread.sleep, и вы увидите взаимоблокировку, потому что вы заблокируете поток и предотвратите запуск RunLoop.

Paulw11 15.05.2024 22:49

«В каждом потоке есть только один стек вызовов»; это тоже не совсем так. Модель диспетчеризации в iOS намного сложнее, чем простые потоки. У вас есть большие центральные очереди отправки, которые сопоставлены с потоками и циклами выполнения, которые управляют отправляемой работой.

Paulw11 15.05.2024 23:39

Каким бы причудливым ни был GCD, он все равно построен на основе потоков. Это означает, что в каждом потоке может быть только один стек вызовов. Итак, в моем примере, если runUntilDate не вызывает мое закрытие, то это наверняка приведет к тупику.

OMGPOP 16.05.2024 05:48

Давайте продолжим обсуждение в чате.

Paulw11 16.05.2024 07:16
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
10
106
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ваш [NSRunLoop.currentRunLoop runUntilDate... выполняет работу основного цикла, поэтому взаимоблокировки не происходит.

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

Ответ принят как подходящий

Вы сказали:

Насколько я понимаю, этот код зашел в тупик. Поскольку блок завершения запускается в основном потоке, для того, чтобы блок завершения был запущен, viewDidLoad должен вернуться. И чтобы viewDidLoad вернулся, необходимо запустить блок завершения, чтобы переключить флаг завершения (следовательно, циклическое ожидание).

Да и нет.

Технически это не тупик, поскольку цикл выполнения все еще обрабатывает события. Ваш пользовательский интерфейс может показаться адаптивным. Но поскольку viewDidLoad не возвращается, некоторые другие события (например, viewDidAppear) вызываться не будут. Будут и другие последствия. Этот код мешает нормальной последовательности событий пользовательского интерфейса, но технически не является «тупиком».

Итог: вы должны разрешить viewDidLoad (и всем событиям UIKit) быстро возвращаться. Итак, либо отправьте это асинхронно (но избегайте использования одной и той же последовательной очереди как для dispatch_async, так и для dispatch_after), или, что лучше, вообще потеряйте это вращение в цикле выполнения.

В наши дни вращение на основной петле почти всегда является ошибкой. GCD предлагает красивые современные решения. Однако, чтобы ответить на этот более широкий вопрос, нам нужно больше подробностей о том, почему вы вообще вращаетесь.

Другие вопросы по теме