iPhone: как использовать метод PerformSelector:onThread:withObject:waitUntilDone:?

Я пытаюсь использовать отдельный поток для работы с некоторыми API.

Проблема в том, что я не могу использовать метод performSelector:onThread:withObject:waitUntilDone: с потоком, который я создал для этого.

Мой код:

@interface MyObject : NSObject {
  NSThread *_myThread;
}
@property(nonatomic, retain) NSThread *myThread;
@end

@implementation MyObject
@synthesize myThread = _myThread;
- (NSThread *)myThread {
  if (_myThread == nil) {
    NSThread *myThreadTemp = [[NSThread alloc] init];
    [myThreadTemp start];
    self. myThread = myThreadTemp;
    [myThreadTemp release];
  }
  return _myThread;
}

- (id)init {
  if (self = [super init]) {
    [self performSelector:@selector(privateInit:) onThread:[self myThread] withObject:nil waitUntilDone:NO];
  }
  return self;
}
- (void)privateInit:(id)object {
  NSLog(@"MyObject - privateInit start");
}

- (void)dealloc {
  [_myThread release];
  _myThread = nil;
  [super dealloc];
}
@end

"MyObject - privateInit start" никогда не печатается.
Что я упускаю?

Я пытался создать поток с целью и селектором, пытался дождаться завершения выполнения метода (waitUntilDone:YES).
Ничего не помогает.

ОБНОВЛЕНИЕ:
Мне не нужна эта многопоточность для разделения дорогостоящих операций на другой поток.
В этом случае я мог бы использовать performSelectorInBackground, как упоминалось в нескольких ответах.
Основная причина для этого отдельного потока заключается в необходимости выполнять все действия в API (TTS от Loquendo) из одного потока.
Это означает, что я должен создать экземпляр объекта TTS и все время вызывать методы для этого объекта из одного и того же потока. .


person Michael Kessler    schedule 06.04.2010    source источник


Ответы (4)


Я нашел ответ!

Чтобы поддерживать поток, необходим дополнительный фрагмент кода:

- (void)threadMain:(id)data {
    NSAutoreleasePool *pool = [NSAutoreleasePool new];

    NSRunLoop *runloop = [NSRunLoop currentRunLoop];
    [runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];

    while (isAlive) { // 'isAlive' is a variable that is used to control the thread existence...
        [runloop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }

    [pool release];
}


И следующая строка:

NSThread *myThreadTemp = [[NSThread alloc] init];

Следует заменить на этот:

NSThread *myThreadTemp = [[NSThread alloc] initWithTarget:self selector:@selector(threadMain:) object:nil];

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

person Michael Kessler    schedule 10.04.2010
comment
Разве не создается бесконечный цикл таким образом? Если циклу выполнения нечего делать, он просто немедленно завершится, и все, что вам нужно сделать, это вызывать - run так часто, как ваш процессор может выдержать. (загрузка ЦП на 100 %) - person bastibe; 13.04.2010
comment
Бесконечный - да. Но цикл не повторяется все время. Я поместил строку журнала (NSLog...) внутри while, и она вызывалась только один раз... - person Michael Kessler; 14.04.2010
comment
Это неожиданное поведение, и на него нельзя полагаться. Цикл выполнения без настроенных источников ввода или таймеров должен немедленно завершиться, и поэтому ваш цикл должен работать постоянно, снова и снова, используя 100% ЦП, как сказал постер. Обратитесь к документации nsrunloop для получения дополнительной информации. - person Benji XVI; 13.07.2010
comment
Пожалуйста, попробуйте решение, прежде чем высказывать подобные заявления и, тем более, прежде чем снижать чью-либо репутацию. ЦП не используется на 100%. - person Michael Kessler; 13.07.2010
comment
Как я уже сказал, в документации четко указано, что NSRunLoop должен завершаться немедленно и должен использоваться 100% процессор. Ваш код ведет себя непредвиденно — либо он использует ошибку для функционирования, либо работает по какой-то другой причине, не представленной здесь в коде. Хороший программист был бы очень обеспокоен этим, а не беспечно отвечал, что код работает. В документации явно указано обратное поведение. - person Benji XVI; 13.07.2010
comment
Хотя мне не доставляет удовольствия уменьшать вашу репутацию, отбрасывание этого ответа было необходимо, чтобы другие люди не увидели этот код и не подумали, что это хорошее решение. Это не так, потому что 1. он использует недокументированное поведение и 2. он не позволяет чисто завершить поток. Если вы хотите, чтобы я удалил отрицательный голос, прочитайте документацию, примите во внимание критику людей и отредактируйте ваш ответ, указав хорошее решение. - person Benji XVI; 13.07.2010
comment
Обратите внимание, что потоки также должны настроить свой собственный пул автовыпуска в соответствии с ответом KermiDT. Чтобы сделать этот ответ приемлемым, 1. добавьте это, 2. добавьте систему проверки активности, чтобы исключить зависимость от недокументированного поведения (согласно KermiDT: добавьте селектор / kotenok-gav: добавьте порт), 3. измените цикл while для проверки условия выхода — do { ... } while (!shouldExit) — и 4. добавить код очистки (релиз autoreleasepool и любые другие переменные) после цикла. - person Benji XVI; 13.07.2010
comment
Вот проект xcode, иллюстрирующий ожидаемое поведение. Соответствующий метод — threadMain в MyController.m. Когда строка, добавляющая фиктивный порт в цикл выполнения, закомментирована, поток привязывается к 100%. Почему этого не происходит в вашем собственном коде, неясно из кода, размещенного здесь, но было бы неразумно продвигаться слишком далеко по маршруту многопоточности без надлежащего исследования. - person Benji XVI; 13.07.2010
comment
@Benji, я проверил ваш проект и еще раз проверил свое решение и пришел к выводу, что «addPort» необходим только в Mac (как следует из многих прогонов, которые я сделал). Возможно, в iPhone это делается в фоновом режиме, чтобы такие изобретатели, как я, не портили приложения для iPhone. Про NSAoutoreleasePool: Раньше я инициировал и выпускал его в каждом методе, который выполняется в отдельном потоке — теперь я понимаю, что так намного проще. О времени (правда): конечно, я не оставил это в своем приложении - просто забыл обновить ответ. Смотрите мой обновленный ответ... - person Michael Kessler; 14.07.2010
comment
Это довольно интересно насчет проектов для iPhone, я посмотрю. Даже Документы NSRunLoop для iPhone (см. методы запуска...) говорят, что цикл выполнения должен завершаться немедленно, когда отсутствуют источники или таймеры. - person Benji XVI; 14.07.2010
comment
Хорошо, я получаю ожидаемое поведение (поток использует 100%) с тривиальным переносом вышеуказанного проекта xcode на iPhone. Но строка, которую вы в настоящее время прокомментировали как не имеющую эффекта в iPhone, определенно необходима, как правило. (Правка: проект xcode iphone — следите за top во время его запуска, чтобы увидеть результат.) - person Benji XVI; 14.07.2010
comment
Я скачал ваш проект и запустил его. Цикл выполнения запускается ТОЛЬКО тогда, когда в этом потоке выполняются действия. Я добавил еще одну кнопку и выполнил селектор в потоке. Цикл выполнения возвращается ТОЛЬКО, когда селектор заканчивается каждый раз. См. скриншоты (dl.dropbox.com/u/2390939/Picture%2011.png и dl.dropbox.com/u/2390939/Picture%2010 .png). - person Michael Kessler; 15.07.2010
comment
Интересный. Попробуйте то же самое, но удалите вызовы NSLog и вместо этого отслеживайте top — потоки зацикливаются бесконечно. По какой-то причине размещение любого вызова NSLog до (или после, в цикле!) [loop run] не позволит получить правильное поведение. Никакого объяснения этому нет, за исключением этой строки из Справочника по основным функциям: Output from NSLogv is serialized, in that *only one thread in a process can be doing the writing/logging described above at a time*. All attempts at writing/logging a message complete before the next thread can begin its attempts. - person Benji XVI; 16.07.2010
comment
А, начнем — кто-то, кто определил такое же взаимодействие между NSLog и NSRunLoop и получение (своего рода) ответа. NSLog настраивает скрытый порт, который предотвращает возврат цикла выполнения — следовательно, он сериализуется, хотя мы не можем подтвердить это, поскольку источник не является общедоступным. Пожалуйста, не могли бы вы удалить строку о том, что это касается только Mac — это не точно, и меня беспокоит, что кто-то это увидит! - person Benji XVI; 16.07.2010
comment
Сделанный. Я ценю ваше исследование. Вы ищете работу? :) - person Michael Kessler; 16.07.2010

Это то, что работает для меня. Основной цикл взят из документации Apple http://developer.apple.com/iphone/library/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16.-SW25

- (void) start {
    self.imageSaverThread = [[[NSThread alloc] initWithTarget:self selector:@selector(imageSaverThreadMain) object:nil] autorelease];
    [self.imageSaverThread start];
}

- (void) imageSaverKeepAlive {
    [self performSelector:@selector(imageSaverKeepAlive) withObject:nil afterDelay:60];    
}

- (void)imageSaverThreadMain
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    // Add selector to prevent CFRunLoopRunInMode from returning immediately
    [self performSelector:@selector(imageSaverKeepAlive) withObject:nil afterDelay:60];
    BOOL done = NO;

    do
    {
        NSAutoreleasePool *tempPool = [[NSAutoreleasePool alloc] init];
        // Start the run loop but return after each source is handled.
        SInt32    result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, YES);

        // If a source explicitly stopped the run loop, or if there are no
        // sources or timers, go ahead and exit.
        if ((result == kCFRunLoopRunStopped) || (result == kCFRunLoopRunFinished))
            done = YES;

        [tempPool release];
    }
    while (!done);

    [pool release];
}

Надеюсь, поможет

person Przemyslaw Zych    schedule 13.07.2010
comment
Теперь это помощь. Обратите внимание, как нужно добавить какое-то событие, чтобы предотвратить немедленный возврат цикла выполнения. - person Benji XVI; 13.07.2010

Ну, я полагаю, у меня есть лучшее решение

- (void)run{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    running = true;
    [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    while (running && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]){
        //run loop spinned ones
    }

    [pool release];
}

Что я здесь делаю?
1) Добавление сюда фиктивного порта в качестве источника предотвратит немедленный выход из метода runMode:beforeDate:.
2) Метод runMode:beforeDate: блокирует поток до тех пор, пока что-то не появится в runLoop.

person Andrey Lushnikov    schedule 07.05.2010
comment
Если я вызову executeSelector:onThread: несколько раз, все ли мои селекторы встанут в очередь? - person onmyway133; 08.07.2013

Вы создали тему, но она не запущена. Он должен работать, чтобы что-то выполнить.

Вместо этого вы также можете использовать «performSelectorInBackground». Он поставит вызов в очередь до завершения инициализации.

person Laurent Etiemble    schedule 06.04.2010
comment
Спасибо за ваш комментарий. [myThreadTemp start]; не должен запускать поток? performSelectorInBackground мне не подходит. Я должен использовать один поток для всех операций с этим API, который я использую (TTS от Loquendo). - person Michael Kessler; 06.04.2010
comment
За время между [myThreadTemp start]; и вашим селектором выполнения поток мог быть остановлен. - person Giao; 06.04.2010
comment
Если вам нужен отдельный поток, вы можете использовать detachNewThreadSelector:toTarget:withObject:. Он создаст и порождает новый поток, который запустится немедленно, поэтому вам не нужно заботиться о создании NSThread. - person Laurent Etiemble; 06.04.2010
comment
Проблема в том, что я должен сохранить ссылку на поток. Я постоянно использую упомянутый API и хочу выполнять ВСЕ действия в этом API из одного потока. Если я использую метод detachNewThreadSelector, я не получу ссылку. Я считаю, что могу получить ссылку из выполненного метода, но я уверен, что поток все равно будет завершен после завершения выполнения метода. - person Michael Kessler; 06.04.2010
comment
Еще одна вещь - если performSelector:onThread:withObject:waitUntilDone: существует, то должен быть способ его использовать. Я не понимаю, как это сделать... - person Michael Kessler; 06.04.2010