I did not find any suitable answers on the web, so I post my question here.
__block int test = 1;
dispatch_async(dispatch_get_main_queue(), ^{
test = 2;
});
NSLog(#"%i",test);
This code will result in console message "1".
__block NSString *test = #"no";
dispatch_async(dispatch_get_main_queue(), ^{
test = #"yes";
});
NSLog(#"%#",test);
This code will result in console message "no".
Why is it so? I thought that __block identifier should solve all problems in this case.
My hypothesis is that local variable was copied and code inside block hadn't actually modify anything outside itself.
How can I modify local variables inside dispatch_async?
Sorry if it is a noob question.
You are dispatching asynchronously to the main queue.
The dispatch_async returns before the block is executed (coincidentally).
To underscore how non-deterministic concurrent programming can be:
Note that your NSLog() might sometimes see the new value maybe once in a very blue moon. You might not ever see it in your debugging environment, but some customer might encounter that behavior 3 years from now on a system configuration that doesn't exist today.
To fix?
dispatch_sync() thereby ensuring that your background queue and the main queue are effectively acting like a less efficient single serial queue.
... or ...
Use some kind of synchronization construct to message from the main queue back to your local queue when the operation is done. I.e.:
dispatch_async(otherQueue, ^{
... do something ...;
dispatch_async(firstQueue, ^{
done(calculatedValue);
};
};
You are dispatching setting test asynchronously, meaning your NSLog statement is going to fire before the block.
You would have to do it this way, or change it to a dispatch_sync.
__block NSString *test = #"no";
dispatch_async(dispatch_get_main_queue(), ^{
test = #"yes";
NSLog(#"%#",test);
});
If you need to do something on a background thread and then pop it back onto the main thread, just do the following:
__block NSString *test = #"no";
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
test = #"setting Testing in the background";
NSLog(#"Printing from the background:%#",test);
dispatch_async(dispatch_get_main_queue(), ^{
test = #"Setting Test on the main thread";
NSLog(#"Logging test on the main thread:%#",test);
});
});
You are calling dispatch_async. Although you are executing the code on the main thread, it is still not determined wether the code "test = #"yes" gets executed before the "NSLog(#"%#",test);" code.
If you use dispatch_sync it will work as expexted.
Related
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
Can anybody see a reason why this code would work fine to update UI:
__block NSDictionary *result = nil;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[[SomeService sharedInstance] doSomethingGreatWithReplyBlock:^(NSDictionary * response) {
result = response;
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
[self updateDisplay:result];
});
But this one won't?
__block NSDictionary *result = nil;
[[SomeService sharedInstance] doSomethingGreatWithReplyBlock:^(NSDictionary * response) {
dispatch_async(dispatch_get_main_queue(), ^{
[self updateDisplay:response];
});
}];
Isn't this exactly the same? In the first example I'm waiting for the async operation to finish using a semaphore. Then dispatch_async on the main queue.
In the second one I'm calling dispatch_async (also on the main queue) directly from within the other block (which runs on some background queue). This one still calls the updateDisplay method fine - however it doesn't actually update the UI. It feels like some main thread update issue however [NSThread isMainThread] still returns true...
Is there any obvious difference I'm missing here? I'm pretty lost here and would appreciate any explanation. I have never observed such weird behavior before.
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.
I have a method that I add to a GCD queue that I have created (so it's a serial queue) and then run it async. From within that block of code I make a dispatch to the main queue, when that block of code dispatched to the main queue is complete I set a BOOL flag to YES, so that I further down in my code can check if this condition is YES then I can continue to the next method. Here is the code in short:
dispatch_queue_t queue = dispatch_queue_create("ProcessSerialQueue", 0);
dispatch_async(queue, ^{
Singleton *s = [Singleton sharedInstance];
dispatch_sync(dispatch_get_main_queue(), ^{
[s processWithCompletionBlock:^{
// Process is complete
processComplete = YES;
}];
});
});
while (!processComplete) {
NSLog(#"Waiting");
}
NSLog(#"Ready for next step");
However this does not work, because dispatch_sync is never able to run the code on the main queue. Is this because I'm running a while loop on the main queue (rendering it busy)?
However if I change the implementation of the while loop to this:
while (!processComplete) {
NSLog(#"Waiting")
NSDate *date = [NSDate distantFuture];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
}
It works without a glitch. Is this an acceptable solution for this scenario? Can I do it any other preferred way? What kind of magic stuff does NSRunLoop do? I need to understand this better.
Part of the main thread's NSRunLoop job is to run any blocks queued on the main thread. By spinning in a while-loop, you're preventing the runloop from progressing, so the queued blocks are never run unless you explicitly make the loop run yourself.
Runloops are a fundemental part of Cocoa, and the documentation is pretty good, so I'd reccommend reading it.
As a rule, I'd avoid manually invoking the runloop as you're doing. You'll waste memory and make make things complicated very quickly if you have multiple manual invocations running on top of one another.
However, there is a much better way of doing this. Split your method into a -process and a -didProcess method. Start the async operation with your -process method, and when it completes, call -didProcess from the completion block. If you need to pass variables from one method to the other, you can pass them as arguments to your -didProcess method.
Eg:
dispatch_queue_t queue = dispatch_queue_create("ProcessSerialQueue", 0);
dispatch_async(queue, ^{
Singleton *s = [Singleton sharedInstance];
dispatch_sync(dispatch_get_main_queue(), ^{
[s processWithCompletionBlock:^{
[self didProcess];
}];
});
});
You might also consider making your singleton own the dispatch queue and make it responsible for handling the dispatch_async stuff, as it'll save on all those nasty embedded blocks if you're always using it asynchronously.
Eg:
[[Singleton sharedInstance] processAyncWithCompletionBlock:^{
NSLog(#"Ready for next step...");
[self didProcess];
}];
Doing something like what you posted will most likely freeze the UI. Rather than freezing up everything, call your "next step" code in a completion block.
Example:
dispatch_queue_t queue = dispatch_queue_create("ProcessSerialQueue", 0);
dispatch_queue_t main = dispatch_get_main_queue();
dispatch_async(queue, ^{
Singleton *s = [Singleton sharedInstance];
dispatch_async(dispatch_get_main_queue(), ^{
[s processWithCompletionBlock:^{
// Next step code
}];
});
});
Don't go creating a loop like that waiting for a value inside a block, variables in blocks are read only, instead call your completion code from inside the block.
dispatch_async(queue, ^{
Singleton *s = [Singelton sharedInstance];
[s processWithCompletionBlock:^{
//process is complete
dispatch_sync(dispatch_get_main_queue(), ^{
//do something on main queue....
NSLog(#"Ready for next step");
});
}];
});
NSLog(#"waiting");
Is it possible to add a block to the end of the current queue, and make sure that this block gets called after all existing items in the queue?
The code below doesn't seem to work:
- (void)someTaskWillBeDoneOnThisThreadLater {
// The current scope is a delegate method of a library I'm using,
// and unfortunately the required task gets executed after this delegate
// method is called.
// wait for current queue to be done with everything, including the current scope
dispatch_async(dispatch_get_current_queue(), ^{
// After everything is done, then call the main thread
dispatch_async(dispatch_get_main_queue(), ^{
// Perform some task on main thread
});
});
}
EDIT:
The following code fixed the problem, but I really don't want to rely on 1 second delay. I rather find a better solution.
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
sleep(1);
// Perform something on main thread
});
});
I think problem is that you need private queue to get this work. This code:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(#"FIRST");
dispatch_async(dispatch_get_current_queue(), ^{
NSLog(#"LAST");
dispatch_async(dispatch_get_main_queue(), ^{
// ...
});
});
sleep(2);
NSLog(#"SECOND");
});
Gives:
2012-07-31 22:57:20.005 Objective-C App[22526:1703] FIRST
2012-07-31 22:57:20.009 Objective-C App[22526:2003] LAST
2012-07-31 22:57:22.010 Objective-C App[22526:1703] SECOND
Which isn't what you wanted. Even dispatch_barrier_async won't help. But when you use private queue:
dispatch_queue_t queue = dispatch_queue_create("test", 0);
dispatch_async(queue, ^{
NSLog(#"FIRST");
dispatch_async(queue, ^{
NSLog(#"LAST");
dispatch_async(dispatch_get_main_queue(), ^{
// ...
});
});
sleep(2);
NSLog(#"SECOND");
});
Will give you result you wanted:
2012-07-31 23:04:41.882 Objective-C App[22564:1703] FIRST
2012-07-31 23:04:43.887 Objective-C App[22564:1703] SECOND
2012-07-31 23:04:43.889 Objective-C App[22564:1703] LAST
As you see "LAST" will be printed at the end and it'll wait 2 seconds until block already on queue is finished.
Although it's not clear that the actual problem is as you've described, there is a facility in GCD, available starting in OS X 10.7 or iOS 4.3 to achieve this: dispatch_barrier_async().
Submitting a Block to a queue using this function causes that Block to wait to execute until all previous Blocks have completed; likewise all subsequent Blocks wait for the barrier to complete.
Note that this is only relevant for concurrent queues, since the nature of a serial queue is that it will not start executing any Block until all previously submitted Blocks have completed.
From the dispatch_barrier_async documentation:
... When the barrier block reaches the front of a private concurrent
queue, it is not executed immediately. Instead, the queue waits until
its currently executing blocks finish executing. At that point, the
barrier block executes by itself. Any blocks submitted after the
barrier block are not executed until the barrier block completes.
So replacing dispatch_async by dispatch_barrier_async in your code should do what you want.
EDIT:
This works only on private (concurrent) queues, see also the #Johnnywho's answer and the following comments.
I overlooked that part of the documentation, sorry.