have a view that loads and a serial dispatch queue that is created, loads a ton of stuff in the background and works great. Problem is when I navigate back and forth to that view a new queue is created again and then I have multiple things doing the exact same work.
- (void)viewDidLoad {
dispatch_queue_t myQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_async(myQueue, ^{
//function call to a helper outside the scope of this view
});
}
How do I prevent this from happening?
EDIT:
creating my own queue was not necessary so I change my code - same problem still exists.
Put it in the initialization code or move myQueue to an instance variable and then check for its existence.
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])
{
dispatch_queue_t myQueue = dispatch_queue_create("com.mydomain.myapp.longrunningfunction", NULL);
dispatch_async(myQueue, ^{
//function call to a helper outside the scope of this view
});
dispatch_async(myQueue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
dispatch_release(myQueue);
});
});
}
return self;
}
Or...
- (void)viewDidLoad {
if(!_myQueue)
{
_myQueue = dispatch_queue_create("com.mydomain.myapp.longrunningfunction", NULL);
dispatch_async(_myQueue, ^{
//function call to a helper outside the scope of this view
});
dispatch_async(_myQueue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
dispatch_release(_myQueue);
});
});
}
}
And if you only want it to run once during a single run of the application you can use dispatch_once
So here is a way to achieve what I really desire, prevent my dispatched queued items from running when my view is popped from the navigation stack:
I simple wrap this code around my code that is running in my dispatched queue:
-(void) myMethod {
if (self.view.window) {
//my code
}
}
This came from watching the Blocks & Multithreading video here by Stanford:
http://itunes.apple.com/us/itunes-u/developing-apps-for-ios-hd/id395605774
great video, helped lot.
If using a storyboard put your initialization in here:
-(void)awakeFromNib{}
Related
MySynchManager class is having a shared instance.
One of the function in MySynchManager class is
- (void)uploadSession:(NSString *)sessionId {
// run the upload process on a separate thread to not to block the main thread for user interaction
// process upload data in serial Queue
NSLog(#"Inside uploadSession");
if (!_serialQueue) {
NSLog(#"uploadSession, _serialQueue is NOT ACTIVE");
[self setRunLoopStarted:FALSE];
_serialQueue = dispatch_queue_create("sessionUploadQueue", NULL);
dispatch_async(_serialQueue, ^{
[[MySyncManager sharedInstance] dispatchSession:sessionId];
});
}
else {
//[self setRunLoopStarted:FALSE];
dispatch_async(_serialQueue, ^{
[self dispatchSession:sessionId];
});
NSLog(#"Adding block to the dispatch queue is complete");
}
}
uploadSession:#"session" is being called from view controllers.
The problem that I am facing is sometimes the code present in dispatchSession is called, but sometimes block is not called.
I only observe the log print statement after the block is printed.
Can any one of you explain the reason behind this?
This is weird code. Try this instead
-(void)uploadSession:(NSString *)sessionId
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_serialQueue = dispatch_queue_create("sessionUploadQueue", NULL);
});
dispatch_async(_serialQueue, ^{
[self dispatchSession:sessionId];
});
}
how can i update GUI elements with values from a queue?
if i use async queue construct, textlable don't get updated.
Here is a code example i use:
- (IBAction)dbSizeButton:(id)sender {
dispatch_queue_t getDbSize = dispatch_queue_create("getDbSize", NULL);
dispatch_async(getDbSize, ^(void)
{
[_dbsizeLable setText:[dbmanager getDbSize]];
});
dispatch_release(getDbSize);
}
Thank you.
As #MarkGranoff said, all UI needs to be handled on the main thread. You could do it with performSelectorOnMainThread, but with GCD it would be something like this:
- (IBAction)dbSizeButton:(id)sender {
dispatch_queue_t getDbSize = dispatch_queue_create("getDbSize", NULL);
dispatch_queue_t main = dispatch_get_main_queue();
dispatch_async(getDbSize, ^(void)
{
dispatch_async(main, ^{
[_dbsizeLable setText:[dbmanager getDbSize]];
});
});
// release
}
Any UI update must be performed on the main thread. So your code would need to modified to use the main dispatch queue, not a queue of your own creation. Or, any of the performSelectorOnMainThread methods would work as well. (But GCD is the way to go, these days!)
How can I make sure that my function is run only on the main thread? It updates UI elements.
Is a function like this considered 'bad'?
-(void)updateSomethingOnMainThread {
if ( ![[NSThread currentThread] isEqual:[NSThread mainThread]] )
[self performSelectorOnMainThread:_cmd withObject:nil waitUntilDone:NO];
else {
// Do stuff on main thread
}
}
I wrote it like this to avoid having a second function, initially I had it like this:
-(void)updateSomethingOnMainThread_real {
// Do stuff on main thread
}
-(void)updateSomethingOnMainThread {
[self performSelectorOnMainThread:#selector(updateSomethingOnMainThread_real) withObject:nil waitUntilDone:NO];
}
As an alternative to ayoy's method-based GCD implementation for guaranteeing execution on the main thread, I use the following GCD-based function in my code (drawn from another answer of mine):
void runOnMainThreadWithoutDeadlocking(void (^block)(void))
{
if ([NSThread isMainThread])
{
block();
}
else
{
dispatch_sync(dispatch_get_main_queue(), block);
}
}
You can then use this helper function anywhere in your code:
runOnMainThreadWithoutDeadlocking(^{
// Do stuff that needs to be on the main thread
});
This guarantees that the actions taken in the enclosed block will always run on the main thread, no matter which thread calls this. It adds little code and is fairly explicit as to which code needs to be run on the main thread.
This is fine. You can also use GCD to execute code on the main thread.
Checkout this SO post.
GCD to perform task in main thread
I wrote this simple #define which I've been using with great success:
#define ensureInMainThread(); if (!NSThread.isMainThread) { [self performSelectorOnMainThread:_cmd withObject:nil waitUntilDone:NO]; return; }
That way your method, assuming it's parameterless, looks like this
- (void) updateTheThings {
ensureInMainThread();
[self.dog setTailWag:YES];
// etc...
Alternatively, you can use Grand Central Dispatch API, but it's not very handy:
-(void)updateSomethingOnMainThread {
void (^doStuff)(void) = ^{
// stuff to be done
};
// this check avoids possible deadlock resulting from
// calling dispatch_sync() on the same queue as current one
dispatch_queue_t mainQueue = dispatch_get_main_queue();
if (mainQueue == dispatch_get_current_queue()) {
// execute code in place
doStuff();
} else {
// dispatch doStuff() to main queue
dispatch_sync(mainQueue, doStuff);
}
}
otherwise, if synchronous call isn't needed, you can call dispatch_async() which is much simpler:
-(void)updateSomethingOnMainThread {
dispatch_async(dispatch_get_main_queue(), ^{
// do stuff
});
}
I'm having a hard time figuring out how to put this all together.
I have a puzzle solving app on the mac.
You enter the puzzle, press a button, and while it's trying to find the number of solutions,
min moves and such I would like to keep the UI updated.
Then once it's finished calculating, re-enable the button and change the title.
Below is some sample code from the button selector, and the solving function:
( Please keep in mind I copy/paste from Xcode so there might be some missing {} or
some other typos.. but it should give you an idea what I'm trying to do.
Basicly, user presses a button, that button is ENABLED=NO, Function called to calculate puzzle. While it's calculating, keep the UI Labels updated with moves/solution data.
Then once it's finished calculating the puzzle, Button is ENABLED=YES;
Called when button is pressed:
- (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";
}
}
Needs to run in background so UI can be updated:
-(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];
});
}
GCD and performSelectorInBackground samples below. But first, let's look at your code.
You cannot wait where you want to in the code above.
Here's the code you had. Where you say wait in the comment is incorrect. See where I added 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;
}
A UI message loop is running on the main thread which keeps the UI running. solvePuzzle is getting called on the main thread so you can't wait - it will block the UI. It also can't set the button back to enabled - the work hasn't been done yet.
It is the worker function's job on the background thread to do the work and then when it's done to then update the UI. But you cannot update the UI from a background thread. If you're not using blocks and using performSelectInBackground, then when you're done, call performSelectorOnMainThread which calls a selector to update your UI.
performSelectorInBackground Sample:
In this snippet, I have a button which invokes the long running work, a status label, and I added a slider to show I can move the slider while the bg work is done.
// 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 Sample:
// 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];
}
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];
});
});
});
}
By and large, any work to be submitted to a background queue needs to follow this pattern of code:
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.
Never Forget:
1 - queue variable above is a reference counted object, because it is a private queue, not a global one. So it is retained by the block which is executing inside that queue. Until this task is complete, it is not released.
2 - Every queue got its own stack which will be allocated / deallocated as part of recursive operation. You only need to worry about class member variables which are reference counted (strong, retain etc.) which are accessed as part of doWork above.
3 - While accessing those reference counted vars inside background queue operation, you need to make them thread-safe, depending on use cases in your app. Examples include writes to objects such as strings, arrays etc. Those writes should be encapsulated inside #synchronized keyword to ensure thread-safe access.
#synchronized ensures no another thread can get access to the resource it protects, during the time the block it encapsulates gets executed.
#synchronized(myMutableArray)
{
//operation
}
In the above code block, no alterations are allowed to myMutableArray inside the #synchronized block by any other thread.
I have a loading screen that I initialise with a label to display the loading progression.
I want to call my DataManager AFTER initialising the loading screen, and then call a method to switch scenes. Here is my code:
-(id) init {
if((self=[super init]))
{
loadingLabel = ....
[self addChild:loadingLabel];
/***** This is what I want to call after the init method
//DataManager loads everything needed for the level, and has a reference to the
//loading screen so it can update the label
[[DataManager sharedDataManager] loadLevel:#"level1" screen:self];
//this will switch the scene
[self finishLoading];
*****/
}
return self;
}
-(void) setLoadingPercentage:(int) perc {
//changes the label
}
-(void) finishLoading {
[[CCDirector sharedDirector] replaceScene:[Game node]];
}
So I can't call the datamanager in the init, because the label will not get updated as content gets loaded, and I can't switch scenes in the init method. So How do I run my datamanger and finish loading AFTER the init? My plan was to set a schedule at an interval of 1 second that does this, but it doesn't seem right to have to wait a second.
EDIT: another way I could do it is to schedule at every frame and ask the datamanager where its at... This seems better already, since the datamanager wouldn't need a reference to the loading screen.
Any ideas?
You can use performSelector:withObject:afterDelay: to force the execution of the specified selector during the next iteration of the current threads run loop:
[self performSelector:#selector(finishLoading) withObject:nil afterDelay:0.0f];
The above answer is correct, but you should use the Cocos2d way to schedule methods to run later:
[self schedule:#selector(finishLoading) interval:0.1];
-(void)finishLoading
{
[self unschedule:#selector(finishLoading)];
//Do your stuff
}