Управление потоками с интенсивным использованием ЦП в iOS

Я опытный программист на C/C++, быстро осваиваю Objective C на iPhone. Я провел много поисков, но не нашел удовлетворительного ответа на, должно быть, общий вопрос; Я извиняюсь, если на это ответят в другом месте, указатели будут оценены.

Мое приложение очень интенсивно использует процессор. Пользовательский интерфейс имеет простой дисплей, показывающий прогресс и кнопку запуска/остановки. Каков наилучший способ выделить максимально возможное количество циклов ЦП для выполнения работы, обеспечивая при этом регулярное обновление дисплея и отзывчивость кнопки запуска/остановки? Я читал, что вы не должны работать в основном потоке, но кроме этого я не нашел много предложений. В свете этого я реализовал свою работу в очереди NSOperation. Я также поместил обновление экрана в свою очередь. Я также щедро посыпал код NSThread sleepForTimeIntervals. Я экспериментировал с разным временем сна от 0,001 до 1 (например, [NSThread sleepForTimeIntervals .1]). Несмотря на это, отображение на экране в лучшем случае вялое (10 секунд), а нажатие кнопки «Стоп» выделяет кнопку, но ничего не происходит снова в течение 10 секунд.

1.) Являются ли очереди NSOperation разумным выбором? Если нет, то что еще? 2.) Как минимизировать сон? (Очевидно, я хочу, чтобы работа выполнялась как можно чаще/в разумных пределах, и я не уверен, что мои сны вообще что-то делают для обновления всего пользовательского интерфейса.) 3.) Есть ли лучший способ сохранить пользовательский интерфейс? своевременно? Например, могу ли я использовать NSTimer или какой-либо другой метод для отправки сообщения пользовательскому интерфейсу с указанием обновить и/или проверить состояние кнопок?

Спасибо за Вашу поддержку.


person user938797    schedule 13.09.2011    source источник


Ответы (4)


1.) Являются ли очереди NSOperation разумным выбором? Если нет, то что еще?

NSOperationQueue звучит так, как будто это было бы разумно.

конечно, у вас есть выбор: pthreads, libdispatch (также известный как GCD), библиотеки потоков C++, построенные поверх pthreads, и т. д., и т. д., и т. д. если вы не создаете много/много, то все сводится к модели, которую вы предпочитаете .

2.) Как минимизировать сон? (Очевидно, я хочу, чтобы работа выполнялась как можно чаще/в разумных пределах, и я не уверен, что мои сны вообще что-то делают для обновления всего пользовательского интерфейса.)

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

3.) Есть ли лучший способ поддерживать пользовательский интерфейс в актуальном состоянии? Например, могу ли я использовать NSTimer или какой-либо другой метод для отправки сообщения пользовательскому интерфейсу с указанием обновить и/или проверить состояние кнопок?

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

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

Обновить

Я не был уверен, что это уместно в модели iOS, но похоже, что это так.

да, это нормально - есть много подходов, которые вы можете использовать. что «лучше» зависит от контекста.

Мое текущее понимание заключается в том, чтобы запускать пользовательский интерфейс в одном потоке (не в основном!),

вы действительно не запускаете пользовательский интерфейс явно; основной поток (обычно) управляется отправкой событий и сообщений в основной поток. основной поток использует цикл выполнения и обрабатывает поставленные в очередь сообщения/события на каждой итерации цикла выполнения. вы также можете запланировать эти сообщения в будущем (подробнее об этом чуть позже). сказав это, все ваши сообщения объектам UIKit и AppKit (если вы нацелены на osx) должны быть в основном потоке (в качестве обобщения, которое вы в конечном итоге узнаете, есть исключения из этого). если у вас есть конкретная реализация, которая полностью отделена от методов обмена сообщениями объектов UIKit, и эта программа является потокобезопасной, то вы можете фактически выполнять эти сообщения из любого потока, поскольку это не влияет на состояние реализации UIKit. простейший пример:

@interface MONView : UIView
@end

@implementation MONView
// ...
- (NSString *)iconImageName { return @"tortoise.png"; } // pure and threadsafe
@end

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

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

@interface MONView : UIView
{
    NSTimer * timer;
    MONAsyncWorker * worker; // << this would be your NSOperation subclass, if you use NSOperation.
}

@end

@implementation MONView

// callback for the operation 'worker' when it completes or is cancelled.
- (void)workerWillExit
{
    assert([NSThread isMainThread]); // call on main

    // end recurring updates
    [self.timer invalidate];
    self.timer = nil;

    // grab what we need from the worker
    self.worker = nil;
    // update ui
}

// timer callback
- (void)timerUpdateCallback
{
    assert([NSThread isMainThread]); // call on main
    assert(self.worker);

    double progress = self.worker.progress;

    [self updateProgressBar:progress];
}

// controller entry to initiate an operation
- (void)beginDownload:(NSURL *)url
{
    assert([NSThread isMainThread]); // call on main
    assert(nil == worker); // call only once in view's lifetime

    // create worker
    worker = [[MONAsyncWorker alloc] initWithURL:url];
    [self.operationQueue addOperation:worker];

    // configure timer
    const NSTimeInterval displayUpdateFrequencyInSeconds = 0.200;
    timer = [[NSTimer scheduledTimerWithTimeInterval:displayUpdateFrequencyInSeconds target:self selector:@selector(timerUpdateCallback) userInfo:nil repeats:YES] retain];
}

@end

обратите внимание, что это очень примитивная демонстрация. также более распространено помещать таймер, обработку обновления и операцию в контроллер представления, а не в представление.

person justin    schedule 13.09.2011
comment
Я привык делать то, что вы описываете в двух последних абзацах. Я не был уверен, что это уместно в модели iOS, но похоже, что это так. Мое текущее понимание состоит в том, чтобы запустить пользовательский интерфейс в одном потоке (не в основном!), запустить мой рабочий поток, использовать таймер для генерации сигнала пользовательскому интерфейсу, чтобы взглянуть на значение прогресса и соответствующим образом обновить индикатор выполнения. Для целей этого конкретного приложения вашего предпоследнего абзаца вполне достаточно, и мне не нужно углубляться в последний абзац (по крайней мере, на данный момент). Спасибо. - person user938797; 14.09.2011

Вы делаете обновления пользовательского интерфейса в основном потоке? Это очень важно, потому что UIKit не является потокобезопасным, и использование его из вторичного потока может привести к вялому поведению (или сбоям, если на то пошло). Обычно вам не нужно использовать sleep в фоновых потоках/очередях, чтобы пользовательский интерфейс оставался отзывчивым (если только ваш пользовательский интерфейс не очень интенсивно использует ЦП, но здесь это не так).

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

NSAssert([NSThread isMainThread], @"UI update not running on main thread");

Простой и легкий способ синхронизировать обновления пользовательского интерфейса с основным потоком — использовать Grand Central Dispatch:

dispatch_async(dispatch_get_main_queue(), ^ { 
    //do your UI updates here... 
});
person omz    schedule 13.09.2011
comment
Спасибо за Утверждение! Пожалуйста, проясните мне кое-что. Ожидается, что я запускаю свой пользовательский интерфейс в основном потоке, а затем рабочие потоки НЕ находятся в основном потоке... или... я также отключаю пользовательский интерфейс от основного потока? Я интерпретирую ваш комментарий как пользовательский интерфейс, который должен быть включен в основной поток, но я хотел бы убедиться. - person user938797; 14.09.2011
comment
Вы должны обновить свой пользовательский интерфейс из основного потока. Обратите внимание, что операции в NSOperationQueue обычно не выполняются в основном потоке, поэтому, если вы обновляете свой пользовательский интерфейс из операции, вы должны явно делать это в основном потоке/очереди (см. 2-й фрагмент кода). - person omz; 14.09.2011
comment
Отлично, кажется, теперь я понимаю. Спасибо. - person user938797; 14.09.2011

Вот вам мои ответы на ваши вопросы.

1) Поскольку вы являетесь опытным программистом на языке C, вы будете чувствовать себя комфортно с Grand Central Dispatch (GCD), основанным на C API для параллелизма.

2) С НОД спать вообще не нужно. Просто отправьте асинхронно работу, которую вам нужно выполнить, в очередь с максимальным приоритетом (DISPATCH_QUEUE_PRIORITY_HIGH).

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

Ознакомьтесь с соответствующей документацией по GCD здесь< /а>.

person Massimo Cafaro    schedule 13.09.2011
comment
Да, я привык к нитям. Я понимаю, что NSOPeration построен поверх GCD и является просто простым входом в него. Но я рад использовать другую модель, если это имеет смысл. Я просмотрел документы GCD (спасибо за указание), и я готов сделать свою собственную рассылку. № 2 и № 3 — это модели, к которым я привык (из Windows), и я рад пойти по этому пути. Спасибо. - person user938797; 14.09.2011

У меня был бы объект модели, который выполняет задачи ЦП, который имеет обратный вызов делегата при изменении вывода и контроллер представления. В viewDidLoad вы устанавливаете контроллер представления в качестве делегата вашей модели. Таким образом, модель может использовать потоки и отправлять сообщения обратно в основную очередь после обновления рассчитанных данных. Если ваш случай не является особенно сложным, просто используйте Grand Central Dispatch и dispatch_async интенсивную задачу в другой поток.

Конечно, вы не должны вызывать sleepForTimeInterval где бы то ни было, чтобы достичь того, чего вы хотите.

person Benjamin Mayo    schedule 13.09.2011
comment
Отлично, это модель, которую я буду использовать (что подтверждается и другими ответами. Спасибо всем за вашу помощь! - person user938797; 14.09.2011