GCD, потоки, поток программ и обновление пользовательского интерфейса - PullRequest
15 голосов
/ 03 сентября 2011

Мне трудно понять, как все это собрать. У меня есть приложение для решения головоломок на Mac. Вы входите в головоломку, нажимаете кнопку, и пока она пытается найти количество решений, мин движется и такое я хотел бы, чтобы интерфейс обновлялся. Затем, как только он завершит вычисление, снова включите кнопку и измените заголовок.

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

Обычно пользователь нажимает кнопку, эта кнопка ВКЛЮЧЕНА = НЕТ, функция вызывается для вычисления головоломки. Пока он рассчитывается, обновляйте метки пользовательского интерфейса с помощью данных ходов / решения. Затем, когда он закончил вычисление головоломки, кнопка ВКЛЮЧЕНА = ДА;

Вызывается при нажатии кнопки:

- (void) solvePuzzle:(id)sender{
    solveButton.enabled = NO;
    solveButton.title = @"Working . . . .";

    // I've tried using this as a Background thread, but I can't get the code to waitTilDone before continuing and changing the button state.
    [self performSelectorInBackground:@selector(createTreeFromNode:) withObject:rootNode];

    // I've tried to use GCD but similar issue and can't get UI updated.
    //dispatch_queue_t queue = dispatch_queue_create("com.gamesbychris.createTree", 0);
    //dispatch_sync(queue, ^{[self createTreeFromNode:rootNode];});

    }

    // Need to wait here until createTreeFromNode is finished.
    solveButton.enabled=YES;
    if (numSolutions == 0) {
    solveButton.title = @"Not Solvable";
    } else {
        solveButton.title = @"Solve Puzzle";
    }
}

Необходимо запустить в фоновом режиме, чтобы пользовательский интерфейс можно было обновлять:

-(void)createTreeFromNode:(TreeNode *)node
{
   // Tried using GCD
   dispatch_queue_t main_queue = dispatch_get_main_queue();

 ...Create Tree Node and find Children Code...

if (!solutionFound){
    // Solution not found yet so check other children by recursion.
   [self createTreeFromNode:newChild];
   } else {
   // Solution found.
   numSolutions ++;
   if (maxMoves < newChild.numberOfMoves) {
       maxMoves = newChild.numberOfMoves;
    }
    if (minMoves < 1 || minMoves > newChild.numberOfMoves) {
        solutionNode = newChild;
        minMoves = newChild.numberOfMoves;

        // Update UI on main Thread

        dispatch_async(main_queue, ^{
                        minMovesLabel.stringValue = [NSString stringWithFormat:@"%d",minMoves];
                        numSolutionsLabel.stringValue = [NSString stringWithFormat:@"%d",numSolutions];
                        maxMovesLabel.stringValue = [NSString stringWithFormat:@"%d",maxMoves];
                    });
                }                        

Ответы [ 3 ]

25 голосов
/ 03 сентября 2011

GCD и выполните образцы SelectInBackground ниже. Но сначала давайте посмотрим на ваш код.

Вы не можете ждать, где вы хотите в коде выше. Вот код, который вы имели. Где вы говорите, ждать в комментарии неверно. Смотрите, где я добавил NO.

- (void) solvePuzzle:(id)sender{
    solveButton.enabled = NO;
    solveButton.title = @"Working . . . .";

    // I've tried using this as a Background thread, but I can't get the code to waitTilDone before continuing and changing the button state.
    [self performSelectorInBackground:@selector(createTreeFromNode:) withObject:rootNode];

    // NO - do not wait or enable here.
    // Need to wait here until createTreeFromNode is finished.
    solveButton.enabled=YES;

}

Цикл сообщений пользовательского интерфейса работает в основном потоке, который поддерживает работу пользовательского интерфейса. Функция executePuzzle вызывается в главном потоке, поэтому вы не можете ждать - он заблокирует пользовательский интерфейс. Он также не может вернуть кнопку в положение «включено» - работа еще не выполнена.

Работа рабочей функции в фоновом потоке заключается в выполнении работы, а затем, когда это делается, для обновления пользовательского интерфейса. Но вы не можете обновить пользовательский интерфейс из фонового потока. Если вы не используете блоки и используете executeSelectInBackground, то когда вы закончите, вызовите executeSelectorOnMainThread, который вызывает селектор для обновления вашего пользовательского интерфейса.

executeSelectorInBackground Пример:

В этом фрагменте у меня есть кнопка, которая вызывает долгосрочную работу, метка состояния, и я добавил ползунок, чтобы показать, что я могу переместить ползунок, пока работа bg завершена.

// on click of button
- (IBAction)doWork:(id)sender
{
    [[self feedbackLabel] setText:@"Working ..."];
    [[self doWorkButton] setEnabled:NO];

    [self performSelectorInBackground:@selector(performLongRunningWork:) withObject:nil];
}

- (void)performLongRunningWork:(id)obj
{
    // simulate 5 seconds of work
    // I added a slider to the form - I can slide it back and forth during the 5 sec.
    sleep(5);
    [self performSelectorOnMainThread:@selector(workDone:) withObject:nil waitUntilDone:YES];
}

- (void)workDone:(id)obj
{
    [[self feedbackLabel] setText:@"Done ..."];
    [[self doWorkButton] setEnabled:YES];
}

Образец GCD:

// on click of button
- (IBAction)doWork:(id)sender
{
    [[self feedbackLabel] setText:@"Working ..."];
    [[self doWorkButton] setEnabled:NO];

    // async queue for bg work
    // main queue for updating ui on main thread
    dispatch_queue_t queue = dispatch_queue_create("com.sample", 0);
    dispatch_queue_t main = dispatch_get_main_queue();

    //  do the long running work in bg async queue
    // within that, call to update UI on main thread.
    dispatch_async(queue, 
                   ^{ 
                       [self performLongRunningWork]; 
                       dispatch_async(main, ^{ [self workDone]; });
                   });    
}

- (void)performLongRunningWork
{
    // simulate 5 seconds of work
    // I added a slider to the form - I can slide it back and forth during the 5 sec.
    sleep(5);
}

- (void)workDone
{
    [[self feedbackLabel] setText:@"Done ..."];
    [[self doWorkButton] setEnabled:YES];
}
3 голосов
/ 30 июля 2014
  dispatch_queue_t backgroundQueue;

  backgroundQueue = dispatch_queue_create("com.images.bgqueue", NULL);        


    - (void)process {    
    dispatch_async(backgroundQueue, ^(void){
    //background task
        [self processHtml];
    dispatch_async(main, ^{ 
// UI updates in main queue
   [self workDone]; 
    });

    });  
    });    
 }
0 голосов
/ 04 августа 2014

В общем, любая работа, которая должна быть отправлена ​​в фоновую очередь, должна следовать следующему шаблону кода:

dispatch_queue_t queue = dispatch_queue_create("com.myappname", 0);

__weak MyClass  *weakSelf = self;  //must be weak to avoid retain cycle

//Assign async work
dispatch_async(queue, 
^{ 
   [weakSelf doWork]; 
   dispatch_async(dispatch_get_main_queue(), 
   ^{ 
      [weakSelf workDone]; 
    });
 }); 
 queue = nil;  //Using ARC, we nil out. Block always retains the queue.

Никогда не забывать:

1 -Приведенная выше переменная очереди - это объект с подсчетом ссылок , потому что это личная очередь, а не глобальная.Таким образом, он сохраняется блоком, который выполняется внутри этой очереди.До тех пор, пока эта задача не будет выполнена, она не освобождается.

2 - У каждой очереди есть свой собственный стек, который будет выделяться / освобождаться как часть рекурсивной операции.Вам нужно беспокоиться только о переменных-членах класса, которые подсчитывают ссылки (сильные, сохраняют и т. Д.), Доступ к которым осуществляется как часть вышеупомянутого doWork.

3 - при доступе к этим переменным с подсчетом ссылок в фоновой операции очереди вам необходимочтобы сделать их потокобезопасными, в зависимости от вариантов использования в вашем приложении.Примеры включают в себя записи в объекты, такие как строки, массивы и т. Д. Эти записи должны быть заключены в ключевое слово @synchronized, чтобы обеспечить потокобезопасный доступ.

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

@synchronized(myMutableArray)
{
    //operation
}

В приведенном выше блоке кода никаких изменений не происходитразрешено myMutableArray внутри блока @synchronized любым другим потоком.

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