So I'm posting an array of images to my server. I want to use GCD to post the array asynchronously, but I also want to make the method in which this happens synchronous so that I can pass back a single response object. However the method dispatch_group_wait seems to be returning immediately (and not waiting for my blocks to finish). Is this an issue because I'm using a block within a block?
NSArray *keys = [images allKeys];
__block NSMutableDictionary *responses = [NSMutableDictionary dictionaryWithCapacity:[images count]];
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < [keys count]; i++) {
__block NSString *key = [keys objectAtIndex:i];
dispatch_group_async(group, queue, ^{
[self postImage:[images objectForKey:key] completionHandler:^(ServerResponse *response){
#synchronized(responses){
if ([response succeeded]) {
NSString *value = [[response data] objectForKey:#"image_token"];
[responses setObject:value forKey:key];
NSLog(#"inside success %#",responses);
} else {
NSString *error = response.displayableError;
if (!error) {
error = #"Sorry something went wrong, please try again later.";
}
[responses setObject:error forKey:#"error"];
[responses setObject:response forKey:#"response"];
}
}
}];
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);
I want to simply wait for all of the [self postImage] methods to callback from the server and modify the responses dictionary.
Jonathan's semaphore example is on target. However, I mentioned using a condition variable as an alternative, so I thought I'd at least post an example. In general, a CV can be used to wait on more general conditions other than just N workers.
Note that condition variables have their place (though not necessarily here), usually best when a lock is already necessary to mutate shared state, then other threads can just wait for a specific condition.
NSUInteger numKeys = [keys count];
NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:numKeys];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (NSUInteger i = 0; i < numKeys; i++) {
__block NSString *key = [keys objectAtIndex:i];
dispatch_async(queue, ^{
[self postImage:[images objectForKey:key] completionHandler:^(ServerResponse *response){
// Basically, nothing more than a obtaining a lock
// Use this as your synchronization primitive to serialize access
// to the condition variable and also can double as primitive to replace
// #synchronize -- if you feel that is still necessary
[conditionLock lock];
...;
// When unlocking, decrement the condition counter
[conditionLock unlockWithCondition:[conditionLock condition]-1];
}];
});
}
// This call will get the lock when the condition variable is equal to 0
[conditionLock lockWhenCondition:0];
// You have mutex access to the shared stuff... but you are the only one
// running, so can just immediately release...
[conditionLock unlock];
Without seeing the code for -postImage:completionHandler:, it's hard to say where things are getting scheduled, but I'm assuming they call through to something provided by iOS. If so, the handler blocks inside your block are dispatched to the global queue asynchronously, and then iOS-supplied function or method is returning immediately. As far as your dispatch group is concerned, the work is done almost instantly.
There's no easy way to get the group to wait for work that isn't already scheduled by the time to call to dispatch_group_wait() is made. However, we can add a lower-level thingamajigger called a semaphore that ensures our actions complete in the right order, and schedule it outside the scope of the inner (asynchronous) blocks.
NSUInteger numKeys = [keys count];
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (NSUInteger i = 0; i < numKeys; i++) {
__block NSString *key = [keys objectAtIndex:i];
dispatch_group_async(group, queue, ^{
// We create a semaphore for each block here. More on that in a moment.
// The initial count of the semaphore is 1, meaning that a signal must happen
// before a wait will return.
dispatch_semaphore_t sem = dispatch_semaphore_create(1);
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
[self postImage:[images objectForKey:key] completionHandler:^(ServerResponse *response){
...
// This instructs one caller (i.e. the outer block) waiting on this semaphore to resume.
dispatch_semaphore_signal(sem);
}];
// This instructs the block to wait until signalled (i.e. at the end of the inner block.)
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
// Done with the semaphore. Nothing special here.
dispatch_release(sem);
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// Now all the tasks should have completed.
dispatch_release(group);
There's a problem here though. Semaphores are a kernel resource. What if we have 100 tasks to perform, but the kernel can only supply 99 semaphores? Bad Things™ happen. We can rebuild the code to use only one semaphore, though waiting for it will appear a bit wonky. Doing so actually obviates the dispatch group entirely, by the way, so we're essentially replacing the group with the semaphore. Let's watch!
NSUInteger numKeys = [keys count];
// set the count of the semaphore to the number of times it must be signalled before
// being exhausted. Up to `numKeys` waits will actually wait for signals this way.
// Additional waits will return immediately.
dispatch_semaphore_t sem = dispatch_semaphore_create(numKeys);
for (int i = 0; i < numKeys; i++) {
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (NSUInteger i = 0; i < numKeys; i++) {
__block NSString *key = [keys objectAtIndex:i];
dispatch_async(queue, ^{
[self postImage:[images objectForKey:key] completionHandler:^(ServerResponse *response){
...;
// This decrements the semaphore's count by one. The calling code will be
// woken up by this, and will then wait again until no blocks remain to wait for.
dispatch_semaphore_signal(sem);
}];
});
}
// At this point, all the work is running (or could have already completed, who knows?).
// We don't want this function to continue running until we know all of the blocks
// have run, so we wait on our semaphore a number of times equalling the number
// of signals we expect to get. If all the blocks have run to completion before we've
// waited for all of them, the additional waits will return immediately.
for (int i = 0; i < numKeys; i++) {
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}
// Now all the tasks should have completed.
dispatch_release(sem);
Yes as has been stated, dispatch_group_wait() is not waiting because the postImage:completionHandler: call seems to be asynchronous. And if you REALLY need this block of code to execute synchronously then a semaphore or lock would seem to be an appropriate solution.
If, however you just want to collect all of the responses to one dictionary for processing, I believe that the most appropriate solution is to use GCD to it's full extent. And to use a dispatch queue to manage the mutable dictionary; This seems to be the solution preferred by apple in most of the documents I have seen.
The crux of the solution is to essentially transfer ownership of the mutable dictionary to a single queue and then only modify it from that queue. The 'ownership' I refer to is not object ownership in the memory management sense, but ownership in the right to modify sense.
I would consider doing something like this:
NSArray *keys = [images allKeys];
// We will not be reasigning the 'responses' pointer just sending messages to the NSMutableDictionary object __block is not needed.
NSMutableDictionary *responses = [NSMutableDictionary dictionaryWithCapacity:[images count]];
// Create a queue to handle messages to the responses dictionary since mutables are not thread safe.
// Consider this thread the owner of the dictionary.
dispatch_queue_t responsesCallbackQueue = dispatch_queue_create("com.mydomain.queue", DISPATCH_QUEUE_SERIAL);
for (NSString *key in keys) {
// This async call may not be needed since postImage:completionHandler: seems to be an async call itself.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self postImage:[images objectForKey:key] completionHandler:^(ServerResponse *response){
dispatch_async(responsesCallbackQueue, ^{
[responses setObject:response forKey:key];
// Perhaps log success for individual requests.
if (responses.count == keys.count){
NSLog(#"All requests have completed");
// All responses are available to you here.
// You can now either transfer back 'ownership' of the dictionary.
dispatch_async(dispatch_get_main_queue(), ^{
[self requestsHaveFinished:responses];
});
}
});
}];
});
}
Related
I would like to update a property of my ViewController self.matchedUsers, which takes data from a query that I run asynchronously through a block.
Then somewhere later When I retrieve the count via [self.matchedUsers count], I still get 0, despite knowing that multiple objects was added to my property. My question is, how do I ensure that my property gets updated even when I am retrieving data asynchronously through a block? Thanks!
Update:
For context, here is the block:
//Way earlier in an initializer:
self.matchedUsers = [[NSMutableArray alloc] init];
//In a method much later
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error){
//Code that updates self.matchedUsers with the NSArray objects
dispatch_async(dispatch_get_main_queue(), ^{
[self.matchedUsers addObjectsFromArray: objects];
});
//Question relates to ensure how property could be updated
}
}];
This should work provided you didn't forget to initialize matchedUsers, you check for its value after it's been changed and array does not lose its elements between the time you schedule and execute the block.
However, I would prefer to write a method that can be called from any thread, say
- (void)addUser ...
#synchronized(self.usersToAdd) {
[self.usersToAdd addObjectsFromArray: array];
Enqueue on main thread {
NSArray *addingNow;
#synchronized(self.usersToAdd) {
addingNow = [self.usersToAdd copy];
[self.usersToAdd removeObjects...
}
if (addingNow.count) {
[self.users addObjectsFromArray: addingNow;
[self.tableView insertRowsWithIndexPaths...
}
}
}
}
As others have written the problem could be missing initialization of matchedUsers but...
...the problem could also be due to your main thread being blocked. You write that you "somewhere later retrieve the count". If that is within the same method as the one that made the first dispatch you will be in trouble. Consider this code
NSMutableArray *collection = [[NSMutableArray alloc] init];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSArray *array = [[NSArray alloc] initWithObjects:#"1", nil];
dispatch_async(dispatch_get_main_queue(), ^{
for (NSString *item in array){
[collection addObject:item];
}
NSLog(#"A");
});
});
[NSThread sleepForTimeInterval:5];
NSLog(#"B");
If this is running on the main thread it will output first B on then A (no matter the sleep time), because the block is not run until the method finishes executing. If you on the other hand dispatch to another global queue instead of the main queue it will be A and then B.
This may be a naive question, but I'll ask it anyway as I cannot find any documentation that clears up this issue in my head.
I'm running iOS5.1 both on device and in the simulator with Xcode45-DP4.
I have a loop that iterates over an array of a number of instances of a class. In that loop I use performSelector on the instances to start a thread that does some relatively slow network operations — pulling down data that I'd rather do in the background.
[arrayOfFriends enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
Friend *f = (Friend*)obj;
iOSSLog(#"%d", idx);
[f performSelectorInBackground:#selector(showDescription) withObject:nil];
-(void)fetchTwitterStatus
{
iOSSLog(#"Trying to fetch twitterstatus %# %#", self.hash, self.twitterUserName);
[mLocalTwitterUser fetchTwitterAPIUserStatusWithScreenName:twitterUserName
withCompletionHandler:^(NSArray *arrayOfStatus, NSError *error) {
if(error) {
iOSSLog(#"%#", error);
} else {
iOSSLog(#"Got twitterstatus %# %d", self.twitterUserName, [arrayOfStatus count]);
#synchronized(items) {
[items addObjectsFromArray:arrayOfStatus];
}
}
}];
}
In my test case there are four instances. Each instance gets its selector, you know..selected. The first three definitely start but only the last actually completes as indicated by the log line that says "Got twitterstatus..." Which is weird.
I can also verify that the method the selector calls "fetchTwitterStatus"
What is the little fundamental nugget of multithreading that I'm missing here?
EDIT: here's fetchTwitterAPIUserStatusWithScreenName...quite a bit here, but effectively it's calling the Twitter API Endpoint user_timeline with a JSON response.
- (void)fetchTwitterUserStatusWithScreenName:(NSString *)screenname
excludeReplies:(BOOL)excludeReplies
withCompletionHandler:(OtterTwitterSearchHandler)completionHandler
{
self.twitterAPIStatusHandler = completionHandler;
//self.fetchTwitterUserStatusHandler = completionHandler;
NSString *urlString = [NSString stringWithFormat:#"https://api.twitter.com/1/statuses/user_timeline.json?screen_name=%#&include_rts=true&include_entities=true&exclude_replies=%#&count=50", screenname, excludeReplies?#"true":#"false"];
NSURL *url = [NSURL URLWithString:urlString];
#warning this isn't the way to do it - just checking the cache for refresh of the scroller
[[ASIDownloadCache sharedCache]removeCachedDataForURL:url];
iOSSRequest *request = [[iOSSRequest alloc] initWithURL:url
parameters:nil
requestMethod:iOSSRequestMethodGET];
NSMutableDictionary *oauthParams = [NSMutableDictionary dictionary];
[oauthParams setObject:[[Twitter sharedService] apiKey] forKey:kASIOAuthConsumerKey];
[oauthParams setObject:[[Twitter sharedService] apiSecret] forKey:kASIOAuthConsumerSecret];
[oauthParams setObject:[self oAuthAccessToken] forKey:kASIOAuthTokenKey];
[oauthParams setObject:kASIOAuthSignatureMethodHMAC_SHA1 forKey:kASIOAuthSignatureMethodKey];
[oauthParams setObject:#"1.0" forKey:kASIOAuthVersionKey];
[oauthParams setObject:[self oAuthAccessTokenSecret] forKey:kASIOAuthTokenSecretKey];
request.oauth_params = oauthParams;
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
if (error) {
if (self.twitterAPIStatusHandler) {
self.twitterAPIStatusHandler(nil, error);
self.twitterAPIStatusHandler = nil;
}
} else {
NSMutableArray *recentStatusForTwitterUser = [[NSMutableArray alloc]init];
NSArray *array = [Twitter JSONFromData:responseData];
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
TwitterStatus *twitterStatus = nil;
twitterStatus = [[TwitterStatus alloc]initWithDictionary:obj];
[recentStatusForTwitterUser addObject:twitterStatus];
}];
if (self.twitterAPIStatusHandler) {
self.twitterAPIStatusHandler(recentStatusForTwitterUser, nil);
self.twitterAPIStatusHandler = nil;
}
}
}];
}
I'd suggest using the asynchronous abstractions already provided where possible. It would be a fairly rare/unique situation where you need to deal with threads directly.
I've found treating each network-based background task as a synchronous NSOperation on a queue works really well.
Get a new instance of NSOperationQueue, configure it, add tasks to it, and manage the queue. The benefit of this approach is that each task can be implemented as a simple synchronous task, and the queue takes care of concurrency. Optionally you can set dependencies (this task must complete before that one).
What is the little fundamental nugget of multithreading that I'm
missing here?
That taking non-multithreaded code and spinning off a random number of threads by performing an arbitrary method in the background is doomed to failure.
Concurrency is a design pattern that must be carefully considered from the start (or is a monumental refactoring effort).
First, you don't want to spawn a thread per network connection. Secondly, given that these are just HTTP requests, you would want to use the systems built in classes for asynchronous HTTP communications. Finally, your concurrency model must exactly specify how you are keeping all data in isolation until you hit whatever mechanism you are using to synchronize the data back into the central store.
Hard to say where that code is going off the rails without seeing more information.
I've really been trying, but I am just not getting blocks very well. I am in the process of using the FMDatabaseQueue, and I am trying to make a very simple queue based query. This is what I have:
-(NSString *) getReferenceForPage:(NSInteger) page
{
[queue inDatabase:^(FMDatabase *db) {
FMResultSet *rs = [db executeQuery:#"SELECT ref_text FROM table WHERE page = ?",[NSNumber numberWithInteger:page]];
if ([rs next]) {
//this is where I get the string
}
}];
return #""; //And this is where I need to return it, but I can't get it to work
}
I don't know why this is so hard for me to grasp, but I need to be able to do something with the string that I am getting from the result set. Ordinarily, I would just return it, but that won't fly here. Can someone shed some light on this?
Thanks
EDIT: I am making calls to my db accessing object in hopes to return a specific value. A lot of these calls will be run on background threads, so I am using this database queue to be thread safe. I have updated the context surrounding the sql query to show what I am needing to do.
It seems your question boils down to "how do I return a value from inside a block back to the calling function?" It's actually rather simple, as long as the block is executed synchronously. Just use a __block variable and assign to it.
__block NSString *result = nil;
[queue inDatabase:^(FMDatabase *db) {
// I'm assuming this is synchronous, because I'm not familiar with the API
FMResultSet *rs = [db executeQuery:#"SELECT ref_text FROM table WHERE page = ?", [NSNumber numberWithInteger:page]];
if ([rs next]) {
result = [[rs acquireStringSomehow] retain];
}
}];
return [result autorelease];
Note, the retain is because there may be an autorelease pool wrapped around the block and we need to make sure the value persists (and then we can autorelease it outside of the block).
If the block is executed asynchronously instead, then you can't possibly have the value get returned from the calling function. If you need to handle that case, then you need to hand the value back to the appropriate thread for later processing, e.g.
NSString *str = [rs fetchStringSomehow];
dispatch_async(dispatch_get_main_queue(), ^{
processString(str);
});
I think you’re missing the difference between synchronous and asynchronous execution. I’m not familiar with the queue API you use, but I’d say that the block that you insert into the DB queue is not executed immediately, synchronously. It’s executed asynchronously, at some later point in time, and only then you will have the resulting string available.
You have two options for your getReferenceForPage method: make it asynchronous, too, or make it synchronous by blocking until the result string is available. The first option is simpler and more desirable. There are several ways to implement it, one of them is passing a block to consume the string when it’s available:
typedef void (^StringConsumer)(NSString*);
-(void) getReferenceForPage: (NSInteger) page consumer: (StringConsumer) consumer
{
[queue inDatabase:^(FMDatabase *db) {
FMResultSet *rs = [db executeQuery:…];
if ([rs next]) {
if (consumer)
consumer([db getResultString]);
}
}];
}
// and in calling code:
[self getReferenceForPage:1 consumer:^(NSString *reference) {
NSLog(#"Got reference: %#", reference);
}];
Another option is to call a known method when the string is available, see Kevin’s answer.
if I do this
NSMutableArray *allColors;
NSData *dataColor = [dictPLIST objectForKey:#"allColors"];
if (dataColor != nil) {
allColors = [NSMutableArray arrayWithArray:
[NSKeyedUnarchiver unarchiveObjectWithData:dataColor]];
}
dataColor = nil;
my allColors mutable array has valid contents, but if I create a GGC group and do this...
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_group_t group = dispatch_group_create();
__block NSMutableArray *allColors;
dispatch_group_async(group, queue, ^{
NSData *dataColor = [dictPLIST objectForKey:#"allColors"];
if (dataColor != nil) {
allColors = [NSMutableArray arrayWithArray:
[NSKeyedUnarchiver unarchiveObjectWithData:dataColor]];
}
dataColor = nil;
});
// .... other stuff is added to the group
dispatch_group_notify(group, queue, ^{
dispatch_group_async(group, queue, ^{
// if I try to access allColors here, the app crashes
});
});
dispatch_release(group);
am I missing something?
thanks.
You are creating an autoreleased array and the autorelease pool is being drained by GCD sometime between when the first block executes and the second block executes.
Any time you are doing concurrent programming, whether by thread or using GCD, you must always hard retain any object that is to survive beyond one scope of execution.
URLs in the array are called one after another. Should it not be called all at once, like nsoperationqueue? Please help me here, Thanks
- (void) allTasksDone {
NSLog(#"DONE");
}
- (void) callMultiple {
dispatch_queue_t myQueue = dispatch_queue_create("com.mycompany.myqueue", 0);
dispatch_group_t group = dispatch_group_create();
NSArray *urls = [NSArray arrayWithObjects:
#"http://www.a.com",
#"http://www.b.com",
#"http://www.c.com",
nil];
for (NSString *url in urls) {
dispatch_group_async(group, myQueue, ^{
NSLog(url);
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
NSHTTPURLResponse *response = NULL;
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
NSLog(#"COMPLETE");
});
}
dispatch_group_notify(group, myQueue, ^{
[self allTasksDone];
});
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self callMultiple];
[self.window makeKeyAndVisible];
return YES;
}
dispatch_queue_create creates FIFO queue. It takes blocks one-by-one from queue in the same order theme were added. If you would like to perform blocks concurrently you can create different queues for each block or get use of one of global queues.
dispatch_queue_t dispatch_get_global_queue(
long priority,
unsigned long flags);
There are 3 global queues, distinguished by priority.
enum {
DISPATCH_QUEUE_PRIORITY_HIGH = 2,
DISPATCH_QUEUE_PRIORITY_DEFAULT = 0,
DISPATCH_QUEUE_PRIORITY_LOW = -2,
};
Those queues does not wait for previous block completion. So your downloads will be performed concurrently.
First, no, async() does not guarantee asynchronous execution of the blocks. That'll only happen if any given block is blocked waiting for something to happen. GCD will then spin up another thread.
However, if the system is already relatively loaded, GCD isn't going to spin up a new thread to do some work if work is already taking place.
Secondly, there is no reason to push NSURLRequests into the background via GCD. NSURLRequest supports asynchronous downloads already.