I'm trying to update and load a UIProgressBar while some code is running on a background thread. When the background thread has finished loading creating an avatar it should update the variable avatarFinishedLoading to TRUE which enables the code within the Open GL ES update method to begin to function.
- (void)viewDidAppear:(BOOL)animated
{
[self performSelectorOnMainThread:#selector(updateProgressBar) withObject:nil waitUntilDone:NO];
dispatch_queue_t loadAvatarAndFrames = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(loadAvatarAndFrames, ^{
[self createAvatar];
dispatch_async(dispatch_get_main_queue(), ^{
avatarFinishedLoading = TRUE;
});
});
}
- (void)updateProgressBar {
float progress = [threadProgressView progress];
if (progress < 1) {
threadProgressView.progress = progress + (float)0.01;
[NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:#selector(updateProgressBar) userInfo:nil repeats:NO];
}else{
[threadProgressView setHidden:TRUE];
}
}
- (void)update
{
// Set colour of background
glClearColor(self.backgroundColour.x, self.backgroundColour.y, self.backgroundColour.z, self.backgroundColour.w);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (avatarFinishedLoading == TRUE){
//Do Some Code
}
}
The application is crashing with the error message: 0xf5a2503: movzwl (%eax,%ecx,2), %eax
exec_bad_access
I cannot figure out where I am going wrong as it seems ok to me.
EDIT
It appears that I have a GL Error: 1280 when executing this code on the background thread. If I run the 'createAvatar' method on the main thread I do not get any GL Errors and the avatar renders as expected
Thanks
There is nothing wrong with the way you set your ivar since that is done on the main thread.
dispatch_async(dispatch_get_main_queue(), ^{
avatarFinishedLoading = TRUE;
});
Is it possible you touch something in -createAvatar method that you should not touch on a background thread?
Related
Adding the following code to a new project cause a flash of a black screen. It seems to be caused by the fade out animation of the splash screen. Unfortunately, the long process must be on main thread. It is possible to avoid it by delaying it but it is unreliable and lengthens the loading process.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// A spinner is shown
// This most probably will not cause a black screen
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// [self loadSomething];
// });
// This will not cause a black screen but not suitable for my use
// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// [self loadSomething];
// });
dispatch_async(dispatch_get_main_queue(), ^{
[self loadSomething];
});
}
- (void)loadSomething
{
NSLog(#"start long process");
NSDate *start = [NSDate date];
while (-[start timeIntervalSinceNow] < 5) {
}
}
Consider why it must be on the main thread and come up with a way to move this processing or minimise it.
You should create a root view controller which displays the app default image and have that view controller displayed (probably with some animation or progress indication) until your processing has completed.
If you're already on your main thread, then there is no need of getting the main thread asynchronously.
dispatch_async(dispatch_get_main_queue(), ^{
});
The above code should be removed.
In case you want to do something or load something, then you should use
performSelectorInBackground
Hope this helps...
It seems that the function call [self updateUI]; blocked by boo.
Is boo run in another background thread or same as foo as the code below?
How can the [self updateUI]; not block by boo?
- (void)MainFunction
{
[self performSelectorInBackground#selector(foo) withObject:nil];
}
- (void)foo
{
[self performSelectorInBackground#selector(boo) withObject:nil];
//updaate UI in MainThread
[self updateUI];
}
- (void)boo
{
//function here take long time to run;
}
In your code seems that you call foo in background and so the UI is updated in the background thread that is not possible because you need to do that in the main thread. In any case, performSelectorInBackground is a little bit old...use the dispatcher in this way:
- (void)MainFunction
{
[self foo];
}
- (void)foo
{
dispatch_async(dispatch_get_global_queue(DISPATCH_PRIORITY_DEFAUL, 0ull), ^{
[self boo];
dispatch_async(dispatch_get_main_queue(), ^{
//updaate UI in MainThread
[self updateUI];
};
};
}
- (void)boo
{
//function here take long time to run;
}
In this case updateUI wait boo, but if you want updateUI before and doesn't matter when boo finish:
- (void)foo
{
dispatch_async(dispatch_get_global_queue(DISPATCH_PRIORITY_DEFAUL, 0ull), ^{
[self boo];
};
[self updateUI];
}
performSelectorInBackground performs the selector on a NEW thread. From Apple docs:
This method creates a new thread in your application, putting your
application into multithreaded mode if it was not already. The method
represented by aSelector must set up the thread environment just as
you would for any other new thread in your program.
If you would like to perform both functions on the SAME background thread, you'll have to declare the background thread (also called queue) as a private member of the class (so it will be accessible from both functions) and perform the selector on that queue
I've been looking at this question to try to solve the problem I have here. The tl;dr is I want to use GCD to let me show a "Waiting" screen while I preform some tasks, then hide the screen when it's done. Right now, I have
- (void) doStuff
{
// Show wait on start
[self.waitScreen setHidden:NO];
dispatch_queue_t queue = dispatch_queue_create("com.myDomain.myApp",null);
dispatch_async(queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
// Double nesting the dispatches seems to allow me to do UI changes as part of 'Code to execute' below.
// If I do not double nest like this, the UI still freezes while it executes
dispatch_queue_t queue2 = dispatch_queue_create("com.myDomain.myApp",null);
dispatch_async(queue2, ^{
dispatch_async(dispatch_get_main_queue(), ^{
// Code to execute
{
//... Do my time consuming stuff here ...
// For testing purposes, I'm using
int i = 0;
while (i < 1000000000)
{
i++;
}
}
// Hide Wait Screen on End
[self.waitScreen setHidden:YES];
});
});
});
});
}
And this works just how I want. I'm calling [self doStuff] like so
- (IBAction) buttonTouchUpInside:(id)sender
{
[self doStuff];
}
- (void) doStuff
{
// ... code from first code block here ...
}
Everything to this point works perfectly. Now, I've discovered I will need to use this in a function call. So I need something like:
- (IBAction) buttonTouchUpInside:(id)sender
{
NSMutableString *string= [self doStuff];
// ... use 'string' to do other stuff ...
// For testing, I'm using
self.label.text = string;
}
- (NSMutableString *) doStuff
{
// ... code from first code block here ...
}
How do I need to change the syntax to be able to pass variables around with dispatch_async?
I looked at the Apple Docs and tried
- (IBAction) buttonTouchUpInside:(id)sender
{
NSMutableString *string= [self doStuff];
// ... use 'string' to do other stuff - shows 'nil' when I put breakpoints here ...
// For testing, I'm using
self.label.text = string;
}
- (NSMutableString *) doStuff
{
__block NSMutableString *string = [[NSMutableString alloc] initWithString:#"Initing"];
// Show wait on start
[self.waitScreen setHidden:NO];
dispatch_queue_t queue = dispatch_queue_create("com.myDomain.myApp",null);
dispatch_async(queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
dispatch_queue_t queue = dispatch_queue_create("com.myDomain.myApp",null);
dispatch_async(queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
// Code to execute
{
int i = 0;
while (i < 1000000000)
{
i++;
}
[string setString:#"Hello World"];
}
// Hide Wait Screen on End
[self.waitScreen setHidden:YES];
});
});
});
});
return string;
}
But when I run this, label just shows Initing. I need it to show Hello World. None of the changes I make in the block are being passed through.
After looking at some other questions, this seems to be referred to as a "race condition". As I understand it, once it hits the dispatch_async, the code in the block starts running on a new thread, but the code outside of the block continues to run at the same time on the old thread. So it looks like the thread outside the block is getting to self.label.text = string before the thread running the block can get to [string setString:#"Hello World"];. How can I make the self.label.text = string line wait until [string setString:#"Hello World"]; finishes?
First of all your reasoning of double nesting is flawed. Not sure why it might have worked, but the correct way is to do some async work, and any time you want to update the ui wrap that code in a block on the main queue.
- (void) doStuff
{
// Show wait on start
[self.waitScreen setHidden:NO];
// queue should be a global variable, you don't want to create it every time you
// execute doStuff
dispatch_async(queue, ^{
// Code to execute
{
//... Do my time consuming stuff here ...
// For testing purposes, I'm using
int i = 0;
while (i < 1000000000)
{
i++;
}
}
dispatch_async(dispatch_get_main_queue(), ^{
// Hide Wait Screen on End
[self.waitScreen setHidden:YES];
});
});
}
Since your queue is performing work asynchronously you can't simply return a value from doStuff without waiting, which will block the queue you call doStuff on again.
If you just want to set a value on a label, do that too in the block executed on the main queue, like hiding the wait screen.
Another common way to do things it to provide a callback block to execute as soon as work is finished.
- (void) doStuffWithCompletionBlock:(void(^)(NSString *))block
{
// again, a global variable for the queue
dispatch_async(queue, ^{
// do some work here that shouldn't block the UI
dispatch_async(dispatch_get_main_queue(), ^{
block(#"My result string");
});
});
}
- (void) myAction:(id)sender
{
__weak typeof(self) weakSelf = self;
[self doStuffWithCompletionBlock:^(NSString *result) {
weakSelf.label.text = result;
}];
}
Notice that I call the completion block on the main queue, this is a choice. You could leave that out, but then you would still have do all UI updates on the main queue later in the completion block itself.
I need to parse a json in the background thread. This parser also needs to update my progress bar using delegate method, but for some reason my progress bar is only updated and the end of the process. My code is the following:
-(void)downloadStuff
{
// some other code
downloaderController.delegate = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[downloaderController parseJson];
});
}
The method parseJson during the process calls the delegate several times to update the progress bar like this:
-(void)updateProgressBar
{
//Is anyone listening
if([_delegate respondsToSelector:#selector(updateProgressBar)])
{
[_delegate updateProgressBar];
}
}
And my updateProgressBar is the following:
(void)updateProgressBar
{
_progressBar.progress =+ 0.05f;
}
Can anyone explain me what am I doing wrong here?
Switch back to the main thread before calling updateProgressBar. You can use GCD or performSelector to do so.
[self.delegate performSelectorOnMainThread:#selector(updateProgressBar) withObject:nil waitUntilDone:NO];
In my application i am using back ground thread for hitting multiple service and perform operation with core data. I have used main thread for back ground process ,Its working fine.
Here is my code
dispatch_queue_t main = dispatch_get_main_queue();
dispatch_async(main,
^{
[self backGroundCall];
});
-(void)backGroundCall
{
NSLog(#"Done");
if([CacheManager refreshDBforFirstTimeUseWithDelegate:self])
{
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"IsDBInitialized"];
ContainerViewController *containerViewControllerInstance = [ContainerViewController getContainerInstance];
[containerViewControllerInstance setUserId:_userID];
[progressView setHidden:YES];
[self.view setUserInteractionEnabled:YES];
[self.navigationController setDelegate:containerViewControllerInstance];
[self.navigationController pushViewController:containerViewControllerInstance animated:YES];
}
}
once i initialize the data base , i need to navigate to the container view.During the initialization i will display one progress bar. That is working fine, when the entire background process is completed(app is in minimized state). During the background process if i come to the foreground progress bar is not showing at that time black screen is display instead of progress view . After the completion of the main threat container view all not display[if i comes to foreground of main thread process].
i need to show the progress bar, if i come back to the app in the middle of the main thread process. Please guide me to fix this issue.
Thanks.
dispatch_queue_t main = dispatch_get_main_queue();
dispatch_async(main,
^{
[self backGroundCall];
});
This is a bit misleading... You call the method backGroundCall, but you are actually doing this on the main thread. If you want to make some operation on a working thread, you can do this:
// Declare the queue
dispatch_queue_t workingQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(workingQueue,
^{
// My background job
dispatch_async(dispatch_get_main_queue(),
^{
// Update the UI
}
);
});