How to use dispatch queue to run function - objective-c

I'm trying to figure out how to get a database fetch to run in the background. Below are the foreground and background version of the same function. The foreground version works. But in the background version the local variable retval never gets assigned. Putting a breakpoint in the pageInfoForPageKey function tells me that function is never called.
Is self available inside the block?
//foreground version
- (PageInfo*)objectAtIndex:(NSInteger)idx
{
return [[self dataController] pageInfoForPageKey:[[[self pageIDs] objectAtIndex:idx] integerValue]];
}
//background version
- (PageInfo*)objectAtIndex:(NSInteger)idx
{
__block PageInfo* retval = nil;
__block NSInteger pageID = [[[self pageIDs] objectAtIndex:idx] integerValue];
dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(aQueue, ^{
retval = [[self dataController] pageInfoForPageKey:pageID];
});
return retval;
}

By using dispatch_async, you are telling the system that you want it to run your block some time soon, and that you don't want to wait for your block to finish (or even start) before dispatch_async returns. That is the definition of asynchronous. That is the definition of “in the background”.
The system is doing what you told it to: it is arranging for your block to run, and then it is returning immediately, before the block has run. So the block doesn't set retval before you return retval, because the block hasn't run yet.
If you want to run the database fetch in the background, you need to change your API to pass retval back (to whoever needs it) at a later time, after the block has run. One way is to pass a completion block as a message argument. This is a common pattern for performing fetches in the background. For example, look at +[NSURLConnection sendAsynchronousRequest:queue:completionHandler:].
You might do it like this:
- (void)fetchObjectAtIndex:(NSIndex)idx completion:(void (^)(PageInfo *))block {
block = [block copy]; // unnecessary but harmless if using ARC
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSInteger pageKey = [[[self pageIDs] objectAtIndex:idx] integerValue];
PageInfo* pageInfo = [[self dataController] pageInfoForPageKey:pageKey];
dispatch_async(dispatch_get_main_queue(), ^{
block(pageInfo);
});
});
}
Then you need to change the caller of objectAtIndex: to use this new API instead.

Related

How to stop NSOperationQueue during dispatch_async

I'm adding many block-operations to an operationsqueue in a for loop. In each operation I need to check on another thread if a condition is fulfilled. If the condition is fulfilled, all operations should be cancelled.
I made a sample code to show you my problem:
__block BOOL queueDidCancel = NO;
NSArray *array = [NSArray arrayWithObjects:#"1",#"2",#"3",#"4",#"5",#"6",#"7",#"8",#"9",#"10", nil];
NSOperationQueue *myQueue = [NSOperationQueue new];
myQueue.maxConcurrentOperationCount =1;
for (NSString *string in array) {
[myQueue addOperationWithBlock:^{
if (queueDidCancel) {return;}
NSLog(#"run: %#", string);
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if ([string isEqualToString:#"1"]) {
queueDidCancel = YES;
[myQueue cancelAllOperations];
}
});
}];
}
Expected output from NSLog:
run: 1
Output I got (it varies between 7 and 9):
run: 1
run: 2
run: 3
run: 4
run: 5
run: 6
run: 7
run: 8
I googled for hours, but I could not find a solution.
I think I found a solution. Here's the updated code:
NSArray *array = [NSArray arrayWithObjects:#"1",#"2",#"3",#"4",#"5",#"6",#"7",#"8",#"9",#"10", nil];
NSOperationQueue *myQueue = [NSOperationQueue new];
myQueue.maxConcurrentOperationCount =1;
for (NSString *string in array) {
[myQueue addOperationWithBlock:^{
[myQueue setSuspended:YES];
NSLog(#"run: %#", string);
dispatch_async(dispatch_get_main_queue(), ^{
if (![string isEqualToString:#"1"]) {
[myQueue setSuspended:NO];
}
});
}];
}
Let me use more space. You need to sync access to your variable. It is the correct idea but you are using it incorrectly. You need a lock or an atomic ivar or something like that to sync access to it.
Then if you cancel in the dispatch_async bit it happens looooong after all the blocks executed. That is what your output shows. As mentioned in the comment, if you add a NSLog e.g.
dispatch_async(dispatch_get_main_queue(), ^{
if ([string isEqualToString:#"1"]) {
queueDidCancel = YES;
// Add here
NSLog(#"Going to cancel now");
[myQueue cancelAllOperations];
}
you will see what I mean. I expect that to typically execute deep into your array or even after all of the array finished executing.
But the biggest problem is your logic. You need some logic to cancel those blocks. Just messaging cancelAllOperations or setSuspended is not enough and blocks that are already running will keep on running.
Here is a quick example.
NSObject * lock = NSObject.new; // Use this to sync access
__block BOOL queueDidCancel = NO;
NSOperationQueue *myQueue = [NSOperationQueue new];
myQueue.maxConcurrentOperationCount =1;
for (NSString *string in array) {
// Here you also need to add some logic, e.g. as below
// Note the sync access
#synchronized ( lock ) {
if (queueDidCancel) { break; }
}
[myQueue addOperationWithBlock:^{
// You need to sync access to queueDidCancel especially if
// you access it from main and the queue or if you increase
// the concurrent count
// This lock is one way of doing it, there are others
#synchronized ( lock ) {
// Here is your cancel logic! This is fine here
if (queueDidCancel) {return;}
}
NSLog(#"run: %#", string);
dispatch_async(dispatch_get_main_queue(), ^{
if ([string isEqualToString:#"1"]) {
// Again you need to sync this
#synchronized ( lock ) {
queueDidCancel = YES;
}
// This is not needed your logic should take care of it ...
// The problem is that running threads will probably
// keep on running and you need logic to stop them
// [myQueue cancelAllOperations];
}
});
}];
}
Now this example does what yours does but with a bit more locking and a bit more logic and NO cancelAllOperations nor suspended = YESs. This will not do what you want as even with this running threads tend to run to completion and you need logic to stop it.
Also, in this example, I left the exit or cancel condition as is in the main thread. Again here this will probably mean nothing gets cancelled, but in real life you'd typically cancel from some UI e.g. a button click and then you'd do it as here. But you could cancel anywhere using the lock.
EDIT
Based on lots of comments here is another possible way.
Here you check inside the block and based on the check add another block or not.
NSOperationQueue * queue = NSOperationQueue.new;
// Important
queue.maxConcurrentOperationCount = 1;
void ( ^ block ) ( void ) = ^ {
// Whatever you have to do ... do it here
xxx
// Perform check
// Note I run it sync and on the main queue, your requirements may differ
dispatch_sync ( dispatch_get_main_queue (), ^ {
// Here the condition is stop or not
// YES means continue adding blocks
if ( cond )
{
[queue addOperationWithBlock:block];
}
// else done
} );
};
// Start it all
[queue addOperationWithBlock:block];
Above I use the same block every time which is also quite an assumption but you can change it easily to add different blocks. However, if the blocks are all the same you will only need one and do not need to keep on scheduling new blocks and then can do it as below.
void ( ^ block1 ) ( void ) = ^ {
// Some logic
__block BOOL done = NO;
while ( ! done )
{
// Whatever you have to do ... do it here
xxx
// Perform check
// Note I run it sync and on the main queue, your requirements may differ
dispatch_sync ( dispatch_get_main_queue (), ^ {
// Here the condition is stop or not
// YES means stop! here
done = cond;
} );
}
};

What could cause UI updates via dispatch_async to fail when called from within a background queue block?

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.

How to synchronously wait for reply block when using NSXPCConnection

I'm using NSXPCConnection and one of my interface call has a reply block, like this:
- (void)addItem:(NSData *) withLabel:(NSString *) reply:(void (^)(NSInteger rc))reply;
Which I call like this:
__block NSInteger status;
[proxy addItem:data withLabel:#"label" reply:^(NSInteger rc)
{
status = rc;
}
];
My understanding is that the reply block run asynchronously, and potentially after the method returns.
I want to test the return code synchronously, what's the best way to do it?
To clarify further the snippet above: the proxy object is the remote object obtained from an NSXPCConnection object using the remoteObjectProxy method. This is an important detail as this impact on which queue the reply block is invoked.
I just found out a probably better way to do this:
Use synchronousRemoteObjectProxyWithErrorHandler instead of remoteObjectProxy when creating the remote object.
No need for semaphore or group.
This is what dispatch groups were made for.
NSTimeInterval timeout = 120; // in seconds
__block NSInteger status;
dispatch_group_t syncGroup = dispatch_group_create();
dispatch_group_enter(syncGroup);
[proxy addItem:data withLabel:#"label" reply:^(NSInteger rc)
{
status = rc;
dispatch_group_leave(syncGroup);
}
];
dispatch_time_t waitTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(NSEC_PER_SEC * timeout));
if(dispatch_group_wait(syncGroup, waitTime) != 0)
{
// complain about a request timing out
}
// enjoy your status
if you chose to use remoteObjectProxyWithErrorHandler to get your proxy, then you need to remember to also put a call to dispatch_group_leave(syncGroup) in your error handler.
I propose to use dispatch_semaphore.
// Create it before the block:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSInteger status = 0;
[proxy addItem:data withLabel:#"label" reply:^(NSInteger rc) {
status = rc;
// In the block you signal the semaphore:
dispatch_semaphore_signal(semaphore);
}
];
// here wait for signal
// I dont remember exactly function prototype, but you should specify here semaphore and the time waiting (INFINITE)
dispatch_semaphore_wait(...);
// in non-ARC environment dont forget to release semaphore
dispatch_release(semaphore);
return status;
I want to test the return code synchronously, what's the best way to
do it?
You really don't want it to run synchronously. That'll just block the queue/thread that the block is running on and generally cause havoc to crashes.
Instead, after the status = rc; line, make a call to something that can process the fact that it is done. Let the method return, let the queue or event loop run, then do the work needed whenever addItem:withLabel: is done.
Like this:
__block NSInteger status;
[proxy addItem:data withLabel:#"label" reply:^(NSInteger rc) {
status = rc;
// like this ...
[someObject processReturnedStatus];
}
];

Waiting for a block of code to execute on a serial dispatch queue

So I have a function that prepares a code block and schedules it to run synchronously on a serial dispatch queue. I'm aware that this serial queue will be run on a new thread. The problem, however, is that the code block modifies a variable that it communicates back to the function which in turn is expected to return it as its return value. The code below will help clarify the situation:
-(DCILocation*) getLocationsByIdentifier: (NSString*) identifier andQualifier: (NSString*) qualifier {
__block DCILocation* retval = nil;
NSString* queryStr = [self baseQueryWithFilterSet:nil];
queryStr = [queryStr stringByAppendingString:#" (identifier = ? OR icao = ?) AND qualifier = ?"];
[self.queue inDatabase:^(FMDatabase *db) {
FMResultSet* results = [db executeQuery:queryStr,
[identifier uppercaseString],
[identifier uppercaseString],
[qualifier uppercaseString]];
if ((nil != results) && [results next]) {
dispatch_async(dispatch_get_main_queue(), ^{
retval = [DCIAirportEnumerator newAirportForStatement:results];
});
[results close];
}
}];
return retval;
}
"self.queue" is the serial dispatch queue that the block will run on. Notice that the block modifies "retval" and updates it by nesting a dispatch_async call to the main thread. The concern however, is that "return retval" (the last line of the function) could be possibly called before the block of code running on the serial dispatch queue is able to modify it. This will result in "nil" being returned.
Any ideas as to how it can be made sure that the function doesn't return until retval as been modified by the block executing on the serial queue?
Any ideas as to how it can be made sure that the function doesn't
return until retval as been modified by the block executing on the
serial queue?
If you need to wait for the result, then your code is still synchronous and you might as well run it in your method instead of on a serial queue. So that's the first option: don't use blocks here.
The second option would be to restructure your code so that it really does run asynchronously. Find the code that depends on the return value retval and break that out into a separate method or block. Then have the block that sets retval call that passing in retval when it's finished.
After your block, you can add
while(YES) {
if(variable) {
break;
}
}
And then add, in your dispatch_async, after you define retval
variable = YES;
You'll just have to define __block BOOL variable = NO; before the block.

Waiting for condition to continue

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");