Objective-C запустить цикл, чтобы остановить и перезапустить метод? - PullRequest
8 голосов
/ 18 июля 2009

Раньше я думал, что я достаточно разумный человек.
«Руководство по программированию потоков» от Apple, разрушило мое эго, поддерживающее самообман.

У меня есть метод, который я хочу запустить несколько раз во вторичном потоке, в приведенном ниже примере я вызвал этот doStuff:
Я хочу иметь возможность многократно останавливать и запускать повторный вызов этого метода.

код запускает поток.
если логическое значение stuffToDo равно true,
тогда он вызывает doStuff:
в противном случае
немного отдыхает.
затем он снова зацикливается, пока я не скажу ему остановиться

Мой текущий код кажется расточительным, потому что он продолжает проверять 'stuffToDo', даже когда нечего делать.

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

Я уверен, что ответ на эффективное решение моих проблем можно найти где-то в разделе «Управление циклами выполнения» в «Руководстве по программированию потоков» от Apple
возможно, это связано с пользовательскими источниками ввода

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

enum eRenderThreadMode
{
    render_not_started,
    render_run,
    render_cancel,
    render_finished
};


- (IBAction) startThread:(id)sender
{
    self.renderThreadMode = render_run;
    label.text = @"doing stuff"; 
    [NSThread detachNewThreadSelector:@selector(keepDoingStuff)  toTarget:self withObject:nil];

}

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

    while (renderThreadMode == render_run) 
    {
        if(stuffToDo)
        {
            [self doStuff];
        }
        else
        {
            [NSThread sleepForTimeInterval:kLittleRest];
        }
    }
    self.renderThreadMode = render_finished;
    [pool release];
}


- (IBAction)stopThread:(id)sender
{
    self.renderThreadMode = render_stop;

    while (self.renderThreadMode == render_cancel) 
    {
        [NSThread sleepForTimeInterval:kLittleRest];
    }
}

Ответы [ 2 ]

7 голосов
/ 18 июля 2009

Да, вы правы, что хотите использовать runloop, вам не хватает только того, как все это настроить. Я собираюсь изменить ваш пост и объяснить, что происходит. Не волнуйтесь, если это вас пугает, это сложно, и есть некоторые ошибки, о которых вы узнаете только из опыта

- (IBAction) startThread:(id)sender
{
    self.renderThreadMode = render_run;
    label.text = @"doing stuff"; 
    self.backgroundThread = [[NSThread alloc] initWithTarget:self selector:@selector(keepDoingStuff) object:nil];
    [self.backgroundThread start];    
}

//Okay, this is where we start changing stuff
- (void)keepDoingStuff
{
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

        //A runloop with no sources returns immediately from runMode:beforeDate:
        //That will wake up the loop and chew CPU. Add a dummy source to prevent
        //it.

        NSRunLoop *runLopp = [NSRunLoop currentRunLoop];

        NSMachPort *dummyPort = [[NSMachPort alloc] init];
        [runLoop addPort:dummyPort forMode:NSDefaultRunLoopMode];
        [dummyPort release];
        [pool release];

        while (1) 
        {
                NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
                [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
                [loopPool drain];
        }
}

Хорошо, на этом этапе вы должны посмотреть на приведенный выше код и подумать: «Ну, это может быть хороший спящий поток, но он ничего не делает. И это правда, но так как он имеет активный runloop мы можем сделать все, что основано на runloop, включая executeSelector: onThread: withObject: waitUntilDone:

- (void) doStuffOnBackgroundThread
{
    [self performSelector:@selector(doStff) onThread:self.backgroundThread withObject:nil waitUntilDone:NO];
}

Когда вы вызываете вышеупомянутый метод в вашем основном потоке (или любом другом потоке), он будет маршалировать различные аргументы и ставить в очередь цикл выполнения указанного потока, вызывая его по мере необходимости. В этом случае это приведет к пробуждению self.backgroundThread из runMode: beforeDate:, выполните -doStuff, затем вернитесь назад к циклу и снова перейдите в режим ожидания в runMode: beforeDate :. Если вы хотите иметь возможность разорвать поток, вы можете установить переменную в цикле while, как вы это делали в своем коде, хотя помните, что поток пропадет, если он спит, если вы его не разбудите, поэтому, вероятно, лучше всего инкапсулировать что в методе, который устанавливает переменную управления с помощью executeSelector: onThread: withObject: waitUntilDone :, в качестве плюса, это будет означать, что переменная будет когда-либо устанавливаться только из фонового потока, что упрощает проблемы синхронизации.

Хорошо, я думаю, что это решит вашу проблему, так что пора сделать обязательный плагин: вы уверены, что хотите делать это с потоками? NSOperation и NSOperationQueue могут быть намного более простым решением, которое позаботится обо всех проблемах с многопоточностью, если все, что вам нужно сделать, - это иногда ставить в очередь некоторые данные для обработки. Они будут планировать работу, управлять зависимостями и создавать / разрушать потоки, а также заботиться обо всех вещах runloop wake / sleep.

7 голосов
/ 18 июля 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...