Ссылка NSRunLoop основного потока на вторичный поток - PullRequest
5 голосов
/ 29 мая 2009

Я только недавно возился с примером приложения, пытаясь полностью обернуть мою голову вокруг NSRunLoop. Образец, который я написал, создал простой вторичный поток через NSOperation. Вторичный поток выполняет несколько задач, таких как обработка NSTimer, а также некоторая элементарная потоковая передача с NSStream. Для выполнения обоих этих входных источников требуется правильно настроенный NSRunLoop.

Мой вопрос такой. Первоначально у меня был некоторый код, который выглядел так во вторичном потоке:

NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
self.connectTimer = [NSTimer scheduledTimerWithTimeInterval:connectTimeout
                                                     target:self
                                                   selector:@selector(connectionConnectedCheck:)
                                                   userInfo:nil 
                                                    repeats:NO];

[myRunLoop addTimer:self.connectTimer forMode:NSDefaultRunLoopMode]; // added source here
[myRunLoop run];

[NSStream getStreamsToHostNamed:relayHost port:relayPort inputStream:&inputStream outputStream:&outputStream];
if ((inputStream != nil) && (outputStream != nil))
{
    sendState = kSKPSMTPConnecting;
    isSecure = NO;

    [inputStream retain];
    [outputStream retain];

    [inputStream setDelegate:self];
    [outputStream setDelegate:self];

    [inputStream scheduleInRunLoop: myRunLoop //[NSRunLoop currentRunLoop]  
                                    forMode:NSRunLoopCommonModes];
    [outputStream scheduleInRunLoop: myRunLoop //[NSRunLoop currentRunLoop] 
                                    forMode:NSRunLoopCommonModes];

    [inputStream open];
    [outputStream open];

    self.inputString = [NSMutableString string];



    return YES;
}

Теперь, с кодом выше, события никогда не будут обрабатываться. Не на currentRunLoop. Впоследствии я сделал что-то ужасное, так как это было просто учебное упражнение, и изменил его так, чтобы оно соответствовало NSRunLoop mainRunLoop. Работал как по волшебству. Тем не менее, я почти уверен, что в зависимости от моих основных потоков цикл выполнения во вторичном потоке находится на 10 различных уровнях неправильно .

Так что мой вопрос состоит из двух частей, и я надеюсь, что все в порядке.

  1. Что может пойти не так с небольшим хаком, который я применил, чтобы заставить вторичный поток работать и отвечать на события через цикл выполнения?

  2. Как правильно настроить вторичный поток для прослушивания всех событий / источников, основанных на таймере, чтобы мне не нужно было выполнять шаг 1.

Спасибо за понимание всем.

Ответы [ 3 ]

6 голосов
/ 01 июня 2009

Отвечая на ваши вопросы в обратном порядке:

2. У тебя две проблемы. Документация для -[NSRunLoop run] гласит:

Если нет входных источников или таймеров привязанный к циклу выполнения, этот метод выходит немедленно; в противном случае он работает приемник в NSDefaultRunLoopMode многократно вызывая runMode:beforeDate:. В других словами, этот метод эффективно начинается бесконечный цикл, который обрабатывает данные из входных источников цикла выполнения и таймеры.

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

Чтобы все работало правильно, ваш цикл выполнения должен сначала иметь несколько входных источников, а затем должен периодически запускаться для проверки событий. Обратите внимание, что вы не хотите использовать [NSRunLoop run], так как вы никогда не получите контроль. Вместо этого я бы рекомендовал установить цикл непосредственно перед return YES, который непрерывно запускает цикл выполнения, пока поток не будет отменен или пока вы не закончите потоковую передачу данных. Это позволит циклу выполнения обрабатывать данные по мере их поступления. Примерно так:

while (!done) {
    [myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
}

[NSRunLoop runUntilDate:] обрабатывает события до указанной даты, а затем возвращает управление вашей программе, чтобы вы могли делать все, что захотите.

1. Это работает, потому что цикл запуска вашего основного потока запускается периодически, поэтому события обрабатываются. Однако, если ваш основной поток заблокирован, ваши данные перестанут поступать. Это может быть особенно плохо, если основной поток ожидает данные из вашего второго потока. Кроме того, NSRunLoop не является потокобезопасным:

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

Руководство по программированию потоков от Apple имеет раздел под названием Run Loop Management , который объясняет все это в некоторой степени. Это не самый ясный документ, который я когда-либо читал, но если вы работаете с циклами выполнения, это хорошее место для начала.

2 голосов
/ 29 мая 2009

Вы не забыли запустить runloop?

[[NSRunLoop currentLoop] run]
1 голос
/ 01 июня 2009

Прежде всего, метод scheduledTimer... должен автоматически добавлять таймер в текущий цикл выполнения. Вам нужно использовать метод addTimer:, только если вы создали таймер с помощью инициализатора initWithFireDate.... Я сомневаюсь, что добавление таймера дважды вызовет проблемы, но это возможно.

Метод run NSRunLoop не должен возвращаться до тех пор, пока не останется больше источников событий или пока цикл не выйдет явно. Это означает, что вам нужно запланировать любые источники событий до вызова run. Переместите [myRunLoop run] вызов в самый конец примера кода. (Также, очевидно, исключите оператор return)

Суть в том, что если возвращается [NSRunLoop run], то у вас нет запланированного источника событий. Используйте отладчик для пошагового выполнения кода. Если вы видите, что он проходит после вызова [NSRunLoop run], вы можете быть уверены, что у вас проблема с источниками входного сигнала.

...