GCD, потоки, поток программы и обновление пользовательского интерфейса
Мне сложно понять, как все это сделать.
У меня есть приложение для решения головоломок на Mac.
Вы входите в головоломку, нажимаете кнопку, и, пытаясь найти количество решений,
мин, и поэтому я хотел бы обновить пользовательский интерфейс.
Затем, как только закончите вычисление, снова включите кнопку и измените заголовок.
Ниже приведен пример кода с помощью селектора кнопок и решающей функции:
(Имейте в виду, что я копирую/вставляю из Xcode, чтобы могли быть некоторые недостающие {} или
некоторые другие опечатки.. но это должно дать вам представление о том, что я пытаюсь сделать.
В основном пользователь нажимает кнопку, эта кнопка ВКЛЮЧЕНА = НЕТ, Функция, вызываемая для вычисления головоломки. Пока он вычисляет, обновляйте ярлыки пользовательского интерфейса с данными о перемещениях/решениях.
Затем, как только закончите вычислять головоломку, кнопка ENABLED = YES;
Вызывается при нажатии кнопки:
- (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];
});
}
Ответы
Ответ 1
GCD и выполните выборкиВыберите образцы ниже. Но сначала посмотрите на свой код.
Вы не можете ждать, когда хотите в коде выше.
Вот код, который у вас был. Если вы говорите, что ждать в комментарии неверно. Смотрите, где я добавил 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;
}
В основном потоке выполняется цикл сообщений пользовательского интерфейса, который поддерживает работу пользовательского интерфейса. solvePuzzle получает вызов в основном потоке, поэтому вы не можете ждать - он заблокирует пользовательский интерфейс. Он также не может включить кнопку для включения - работа еще не выполнена.
Это работа рабочей функции в фоновом потоке, чтобы выполнить эту работу, а затем, когда она была выполнена, чтобы затем обновить интерфейс. Но вы не можете обновлять пользовательский интерфейс из фонового потока. Если вы не используете блоки и используете performSelectInBackground, тогда, когда вы закончите, вызовите функцию executeSelectorOnMainThread, которая вызывает селектор для обновления вашего интерфейса.
выполнить выборкиВыбор выборки:
В этом фрагменте у меня есть кнопка, которая вызывает долговременную работу, метку состояния, и я добавил слайдер, чтобы показать, что я могу перемещать ползунок, когда выполняется работа 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];
}
Ответ 2
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];
});
});
});
}
Ответ 3
В общем, любая работа, которая должна быть отправлена в фоновый режим, должна следовать этому шаблону кода:
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
любым другим потоком.