Вы должны взаимодействовать с пользовательским интерфейсом только из основного потока, а не из фоновых потоков.
Это не просто вопрос взаимодействия с вашими собственными средствами управления; ваши элементы управления могут взаимодействовать с другими объектами в пользовательском интерфейсе за вашей спиной. (Например, ваша кнопка может взаимодействовать с вашим окном.) Это может привести к условиям гонки, взаимоблокировкам, неверному / смешанному состоянию и другим проблемам параллелизма.
Когда вы выполняете некоторую обработку в фоновом потоке, и ему необходимо сообщить результаты (промежуточные или конечные) пользователю, ему нужно будет передать это сообщение через основной поток. В Какао есть несколько механизмов, с помощью которых это можно сделать:
Метод -performSelectorOnMainThread:withObject:waitUntilDone:
позволяет вам сказать «запустите этот другой метод в главном потоке», необязательно ожидая, пока это не будет сделано. Вы должны почти никогда передать YES
для аргумента waitUntilDone:
, поскольку это рецепт тупика.
Начиная с Mac OS X 10.6 и iOS 4.0, существует +[NSOperationQueue mainQueue]
, который возвращает экземпляр NSOperationQueue, связанный с основным потоком: вы можете поместить операции в эту очередь, и они будут выполняться в основном потоке.
Это полезно, если вы используете несколько операций в фоновой очереди, и у вас есть некоторая «завершающая» операция для выполнения в основном потоке, которая зависит от всех них. Вы можете просто использовать механизм зависимостей NSOperation для установки зависимостей между ними, даже если они находятся в разных очередях.
Вы можете использовать подкласс NSOperation или использовать блоки Objective-C для тел ваших операций (через +[NSBlockOperation blockOperationWithBock:]
).
Также начиная с Mac OS X 10.6 и iOS 4.0, есть Grand Central Dispatch, которая позволяет выполнять блок в основной очереди, связанной с основным потоком. Это немного менее многословный API, чем NSOperation, но за счет отсутствия прямой поддержки зависимостей и некоторых других функций NSOperationQueue и построения в простом C, а не в Objective-C.
При использовании любого из этих механизмов следует помнить, что вы не должны одновременно взаимодействовать с одними и теми же данными из нескольких потоков без соответствующего контроля параллелизма (такого как блокировка или использование специализированных структуры данных и примитивы без блокировок). Вы не можете сойти с рук «О, я просто читаю, поэтому мне не нужно снимать блокировку» или «О, мне просто нужно включить кнопку, мне не нужно нажимать это, чтобы основная нить. "
Хороший способ избежать этой проблемы - выполнить как можно больше работы, разбив ее на отдельные единицы, обрабатывая эти единицы в фоновом режиме и передавая результаты в основной поток.
Итак, ваш многопоточный код не написан так:
- раскрутить нить, чтобы:
- заблокировать документ
- получить некоторые данные из документа
- разблокировать документ
- обработка данных
- сообщить основной ветке, включать или отключать кнопку Foo документа
Вместо этого ваш многопоточный код записывается так:
- получить некоторые данные из документа
- раскрутить нить, чтобы:
- обработка данных
- затем сообщите основному потоку результат обработки
- в основной теме:
- определить по результатам обработки, включить или отключить кнопку Foo документа
Разница заключается в том, что последний написан с точки зрения выполняемых единиц работы, а не с точки зрения пользовательского интерфейса, и будет более легким для понимания и более надежным перед лицом жизни вашего приложения (например, добавление функций) - по сути, «MVC применяется к потокам».