my dispatch_async on q1, that calls a UI object on main queue, which adds a callback on q1, crashes - objective-c

It sounds confusing but it looks like this
AVPlayer *capturedPlayer = _player;
dispatch_async(_subtitlesQueue, ^{
// Parse the requested subtitle track and create a subtitle time observer
subripString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
subripEntries = [SubRipParser parse:subripString];
if (!subripEntries.count)
return;
dispatch_async(dispatch_get_main_queue(), ^{
_subtitlesTimeObserver = [capturedPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1, 5)
queue:_subtitlesQueue
usingBlock:^(CMTime time){}];
});
});
The above piece of code is called when a button is clicked. It crashes. I'm new to GCD and the whole queue thing so perhaps I'm misunderstanding, but shouldn't the above work?
If I change the call on the main queue to a synchronous then it works. The crash happens from the subtitleQueue on a call to AVPlayer's makePeriodicCall (or the like).
The async call also works if I add the periodic time observer to the main queue instead of custom serial queue. However, the docs say that adding on a different queue should be ok.
Question 2)
And while I'm here, I also have a question about the part that "captures" the AVPlayer. Is capturing the variable like that safe enough or do I have to use __weak and make sure it's not NULL within the block? My situation is such that the controller that contains the AVPlayer is a singleton, so it exists throughout the lifetime of the application. I think this makes not using the __weak modifier ok. Am I correct in thinking this?
Cheers, and thanks for any help!
EDIT:
The exception is a EXC_BAD_ACCESS code 2, so something which shouldn't be accessed is. It happens on a separate thread that is running the _subtitlesQueue. And it happens on a call to [AVPlayerPeriodicCaller _effectiveRateChanged]
I also printed out the values for the capturedPlayer and _subtitlesQueue (pointer values) before the outer dispatch_async is called on the _subtitlesQueue, before the inner dispatch_async is called on the main queue and inside the dispatch_async on the main queue before the addPeriodicTimeObserver is called. They are all the same.
EDIT2:
If I add a synchronized block around the periodic time observer creation on the subtitleQueue then things work...
#synchronized(_subtitlesQueue) {
_subtitlesTimeObserver = [capturedPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1, 5)
queue:_subtitlesQueue
usingBlock:subtitleTimeObservedBlock];
}
All

There seems to be a bug that causes EXC_BAD_ACCESS in -[AVPlayerPeriodicCaller _effectiveRateChanged] when you add a periodic observer to a playing AVPlayer. The workaround that I'm using is:
BOOL playing = player.rate > 0.0f;
if (playing)
{
[player pause];
}
[player addPeriodicTimeObserverForTimeInterval:myTime queue:mySerialQueue usingBlock:myBlock];
if (playing)
{
[player play];
}
As you pointed out, another workaround is to pass NULL instead of a serial queue, since that has the effect of enqueueing the blocks on the main thread dispatch queue.

Related

How to switch between background and main threads

I've never used background threads before. I have a time consuming computation currently running on the main thread which appends the data output to a TERecord. My workflow essentially goes:
run long process…
update GUI…
run long process…
update GUI…
and so on.
At several places where the code produces (string) output I update the UI by calling my 'addToRecord' method shown here:
-(void)addToRecord:(NSString*)passedStr:(BOOL)updateUI
{
NSRange endRange;
// add the passed text...
endRange.location = [[theOutputView textStorage] length];
endRange.length = 0;
[theOutputView replaceCharactersInRange:endRange withString:passedStr];
if(updateUI) // immediate GUI update needed...
{
// scroll window contents to BOTTOM of page...
endRange = NSMakeRange([[theOutputView string] length],0);
[theOutputView scrollRangeToVisible:endRange];
[theOutputView display];
}
}
While it does the job, my entire UI remains unresponsive until the process completes, of course. I know I should be doing the heavy lifting on a background thread which I've never used before. I've figured out part of the problem in creating a background thread like below:
-(IBAction)readUserInput:(id)sender
{
// irrelevant code snipped for brevity
if([self checkForErrors] == NO)
{
[runButton setEnabled:NO];
[self performSelectorInBackground:#selector(runWorkThread) withObject:nil];
}
}
-(void)runWorkThread
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
[self runLongProcess];
[pool drain];
}
but i just don't understand how to call the main thread every time the code encounters my 'addToRecord' method, then how to return control to the background thread?
Another possibility might be to remove the updateUI code from my 'addToRecord' method and just have have the main thread calling this code every second or so on a timer?
Any advice and sample code would be greatly appreciated. Thanks!
Instead of using performSelectorInBackground you can use the Dispatch framework (also called GCD), which is the preferred way of handling concurrent work. The Dispatch already has a pool of background threads set up that you can use. To switch thread you call dispatch_async() like this:
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
// :
// Do your background work here
// :
dispatch_async(dispatch_get_main_queue(), ^{
// :
// Now you are back in the main thread
// :
});
});
The first parameter is the queue identifier which is supplied to you by either dispatch_get_global_queue() which returns one of the "worker" queues, or dispatch_get_main_queue() which returns the main queue. The last parameter is a code block that is executed on the selected queue.
When requesting a concurrent queue using dispatch_get_global_queue() you specify a Quality of Service, which determines the priority your code will have in relation to other work. See the documentation for more information and possible values.
Read more on the Dispatch

UI does not update when main thread is blocked in Cocoa app

I am using a NSProgressIndicator in my main thread to update on progress as I run through my entire method. Now when I end up calling an object from a different class file, and wait for that object to return to a value to my main thread, I notice that the NSProgressIndicator will disappear. I understand that this is because the main thread is blocked until I get the return value from the other object.
So my questions is what is the recommended way for updating UI in the main thread without blocking it and having other objects run in the background and return values to the main thread as needed. I know how to use blocks but blockoperations are not allowed to return values.
What I need is something that helps this pseudo code:
-(IBAction) main {
//Update progress indicator UI to show progress
//perform an call to another object from another class.
// wait till i get its return value.
//Update progress indicator UI to show progress
// Use this return value to do something.
//Update progress indicator UI to show progress
}
When the call to the other object is made, I notice that the determinate NSProgressIndicator I have completely disappears since the main thread is blocked. Thanks.
Your above code is not the correct approach. Since main never returns, the progress indicator will never update. You must return quickly on the main thread.
Instead, what you want to do is set up a background block that at various points updates the progress indicator on the main thread. So, for instance:
- (IBAction)start:(id)sender {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{[self.progress setProgress:0];});
// Doing some stuff
dispatch_async(dispatch_get_main_queue(), ^{[self.progress setProgress:.25];});
// Doing more stuff
dispatch_async(dispatch_get_main_queue(), ^{[self.progress setProgress:.75];});
});
}
(Yes, this causes the queue to retain self, but that's ok here because self is not retaining the queue.)
You can achieve what you are looking for with GCD (Grand Central Dispatch).
Here is an example to get you started:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
// Perform async operation
dispatch_sync(dispatch_get_main_queue(), ^{
// Update UI
});
});
It sounds like your operation should be run in a separate thread which can be done several ways but is probably most easily achieved using NSOperationQueue and either custom NSOperation classes (it's easier than it sounds to set these up) or use of the NSInvokeOperation class.
Then you can send messages back to your class in the main thread using the NSNotificationCenter or set up as an observer using Key-Value Observing (KVO).
Bottom line, you have a variety of choices and to make the best one should have an understanding of the underlying technologies. I'd start with Apple's Threaded Programming Guide personally, then read it a second time to be sure you extracted all the goodness before building out your solution.

Protecting critical code from being called again

I need to protect a critical area of my code, which is multi-threaded. I want to prevent it from being called multiple times before the other thread is finished. This is what I am working with:
- (void) filterAllEventsIntoDictionary{
// start critical area
if (self.sortedKeys.count != 0) {
[self.sortedKeys removeAllObjects];
}
dispatch_async(self.filterMainQueue, ^{
[self internal_filterAllEventsIntoDictionary];
dispatch_sync(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
});
}
Since the internal_filterAllEventsIntoDictionary method also accesses self.sortedKeys, if this code is called twice, it crashes because of removeAllObjects at the start.
I still need to call the internal... method in another thread since I don't want to block the UI. So what's the best way to block on the start of this method while the dispatch_async call is still not finished?
While I am far from being a concurrency expert, it sounds to me like you need a lock on your sortedKeys object. If you used a traditional lock, though, you'd end up blocking the main thread.
The recommended replacement for locks in the world of Grand Central Dispatch is to put critical sections of code on a serial queue. See "Eliminating Lock-Based Code" in the Concurrency Programming Guide.
If you put the [self.sortedKeys removeAllObjects]; call onto the same queue that the block with the internal... call is scheduled on, you guarantee that it won't happen until after that block completes:
// start critical area
dispatch_async(self.filterMainQueue, ^{
if (self.sortedKeys.count != 0) {
[self.sortedKeys removeAllObjects];
}
});
This assumes that filterMainQueue is serial. Using dispatch_async for the critical section ensures that the main thread will not be blocked. Also note the warning in "Dispatch Queues and Thread Safety":
Do not call the dispatch_sync function from a task that is executing on the same queue that you pass to your function call. Doing so will deadlock the queue.
Although this will only be an issue if the internal... method does something that causes this method to be called again.

How to update UI in a task completion block?

In my application, I let a progress indicator starts animation before I send a HTTP request.
The completion handler is defined in a block. After I get the response data, I hide the progress indicator from inside the block. My question is, as I know, UI updates must be performed in the main thread. How can I make sure it?
If I define a method in the window controller which updates UI, and let the block calls the method instead of updating UI directly, is it a solution?
Also, if your app targets iOS >= 4 you can use Grand Central Dispatch:
dispatch_async(dispatch_get_main_queue(), ^{
// This block will be executed asynchronously on the main thread.
});
This is useful when your custom logic cannot easily be expressed with the single selector and object arguments that the performSelect… methods take.
To execute a block synchronously, use dispatch_sync() – but make sure you’re not currently executing on the main queue or GCD will deadlock.
__block NSInteger alertResult; // The __block modifier makes alertResult writable
// from a referencing block.
void (^ getResponse)() = ^{
NSAlert *alert = …;
alertResult = [NSAlert runModal];
};
if ([NSThread isMainThread]) {
// We're currently executing on the main thread.
// We can execute the block directly.
getResponse();
} else {
dispatch_sync(dispatch_get_main_queue(), getResponse);
}
// Check the user response.
if (alertResult == …) {
…
}
You probably misunderstood something. Using blocks doesn't mean that your code is running in a background thread. There are many plugins that work asynchronously (in another thread) and use blocks.
There are a few options to solve your problem.
You can check if your code is running in the main thread my using [NSThread isMainThread]. That helps you to make sure that you're not in the background.
You can also perform actions in the main or background by using performSelectorInMainThread:SEL or performSelectorInBackground:SEL.
The app immediately crashes when you're trying to call the UI from a bakcground thread so it's quite easy to find a bug.

Async call in Objective-C

I'm trying to get data from a website- xml. Everything works fine.
But the UIButton remains pressed until the xml data is returned and thus if theres a problem with the internet service, it can't be corrected and the app is virtually unusable.
here are the calls:
{
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
if(!appDelegate.XMLdataArray.count > 0){
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[appDelegate GetApps]; //function that retrieves data from Website and puts into the array - XMLdataArray.
}
XMLViewController *controller = [[XMLViewController alloc] initWithNibName:#"MedGearsApps" bundle:nil];
[self.navigationController pushViewController:controller animated:YES];
[controller release];
}
It works fine, but how can I make the view buttons functional with getting stuck. In other words, I just want the UIButton and other UIButtons to be functional whiles the thing works in the background.
I heard about performSelectorInMainThread but I can't put it to practice correctly.
You don’t understand the threading model much and you’re probably going to shoot yourself in the foot if you start adding asynchronous code without really understanding what’s going on.
The code you wrote runs in the main application thread. But when you think about it, you don’t have to write no main function — you just implement the application delegate and the event callbacks (such as touch handlers) and somehow they run automatically when the time comes. This is not a magic, this is simply a Cocoa object called a Run Loop.
Run Loop is an object that receives all events, processes timers (as in NSTimer) and runs your code. Which means that when you, for example, do something when the user taps a button, the call tree looks a bit like this:
main thread running
main run loop
// fire timers
// receive events — aha, here we have an event, let’s call the handler
view::touchesBegan…
// use tapped some button, let’s fire the callback
someButton::touchUpInside
yourCode
Now yourCode does what you want to do and the Run Loop continues running. But when your code takes too long to finish, such as in your case, the Run Loop has to wait and therefore the events will not get processed until your code finishes. This is what you see in your application.
To solve the situation you have to run the long operation in another thread. This is not very hard, but you’ll have to think of a few potential problems nevertheless. Running in another thread can be as easy as calling performSelectorInBackground:
[appDelegate performSelectorInBackground:#selector(GetApps) withObject:nil];
And now you have to think of a way to tell the application the data has been loaded, such as using a notification or calling a selector on the main thread. By the way: storing the data in the application delegate (or even using the application delegate for loading the data) is not very elegant solution, but that’s another story.
If you do choose the performSelectorInBackground solution, take a look at a related question about memory management in secondary threads. You’ll need your own autorelease pool so that you won’t leak autoreleased objects.
Updating the answer after some time – nowadays it’s usually best to run the code in background using Grand Central Dispatch:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// No explicit autorelease pool needed here.
// The code runs in background, not strangling
// the main run loop.
[self doSomeLongOperation];
dispatch_sync(dispatch_get_main_queue(), ^{
// This will be called on the main thread, so that
// you can update the UI, for example.
[self longOperationDone];
});
});
Use NSURLConnection's connectionWithRequest:delegate: method. This will cause the specified request to be sent asynchronously. The delegate should respond to connection:didReceiveResponse: and will be sent that message once the response is completely received.
You can make use of a background operation that gets pushed into the operation queue:
BGOperation *op = [[BGOperation alloc] init];
[[self operationQueue] addOperation:op];
[op release];
I've created specific "commands" that get executed in the background:
#implementation BGOperation
# pragma mark Memory Management
- (BGOperation *)init
{
if ((self = [super init]) != nil)
/* nothing */;
return self;
}
- (void)dealloc
{
self.jobId = nil;
[super dealloc];
}
# pragma mark -
# pragma mark Background Operation
- (void)main
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[appDelegate GetApps];
[pool release];
return;
}
#end
After completion it might be a good idea to send a notification to the main thread because the internal database has been changed.
It looks as if you might be using NSURLConnection inside your getApps method. If so, you should convert it to an asynchronous call.