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

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.

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

Canceling previous call of dispatch_async

I'm implementing a tracking mechanism of window A following the position of window B. Window B sending events of its position and window B reacts to those events by calling to setWindowProperties:
void setWindowProperties(bool topMost, bool visible,
CGWindowID parentWindow, CGWindowID aboveWindow,
NSRect windowFrame, NSRect viewFrame, bool isAbove)
{
dispatch_async(dispatch_get_main_queue(), ^{
setWindowPropertiesImpl(topMost, visible, parentWindow, aboveWindow, windowFrame, viewFrame, isAbove);
});
}
But, because of too much events sent by window B I'm getting a "snake tracing" effect. I want to get over it by reacting only to the last position event, meaning, canceling all previous call to :
dispatch_async(dispatch_get_main_queue(), ^{
setWindowPropertiesImpl(topMost, visible, parentWindow, aboveWindow, windowFrame, viewFrame, isAbove);
});
And as a result, leaving in the queue only the last position event - the only one that matters.
My question: Is there a way to cancel all previous calls for of dispatch_async?
Yes, dispatch tasks are now cancelable, but when there are events that are coming in more quickly than the main queue can process them, it's sometimes useful to use a dispatch source. Specifically a DISPATCH_SOURCE_TYPE_DATA_ADD data source.
// create source (and save this reference somewhere so it doesn't get released on you)
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
// specify what you want the event handler to do
dispatch_source_set_event_handler(source, ^{
// whatever you want to do
});
// start the dispatch source
dispatch_resume(source);
Then, when you want to trigger this, rather than doing dispatch_async, you would:
dispatch_source_merge_data(source, 1);
Clearly, this means that the event handler has to pull the data from the other window rather than pushing it, but hopefully this illustrates the basic idea.
For more information see WWDC 2012 video Asynchronous Design Patterns with Blocks, GCD, and XPC. Specifically, see design pattern 8, "Update State Asynchronously" in the latter part of the video.
You cannot cancel an operation enqueued on a dispatch queue.
GCD queues have no way of cancelling a block once it scheduled. The architecture is very much "fire and forget".
Instead of GCD you could use NSOperationQueue that can also async executes, then you can cancel.

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

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.

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.