darksky
darksky

Reputation: 21059

GCD - Asynchronous Queue with Synchronous Tasks

I am trying to download data from an API before displaying it to the user.

Here's what I am doing:

dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{

    __block NSMutableArray *newImages;

    dispatch_sync(concurrentQueue, ^{
        newImages = [NSMutableArray array];
        // images retrieved using `NSURLConnection sendSynchronousRequest`
    });

    dispatch_sync(dispatch_get_main_queue(), ^{
        // display images to the user
    });
});

My question is, since newImages is declared with __block, am I always guaranteed to have the latest newImages data when executing the second task at the main queue? If not, what's the safest way to pass that array's content into the second block and making sure its the latest content?

I think my data is getting messed up somewhere, hence why I am asking this.

If this looks fine, then I will post my full code for help with the whole thing in case something else is wrong.

Upvotes: 2

Views: 2123

Answers (4)

Jeremy W. Sherman
Jeremy W. Sherman

Reputation: 36143

A sketch of a solution:

q = /* custom serial queue */
urls = /* urls array */;
NSMutableArray *images = [NSMutableArray new];
for (NSURL *url in URLs) {
   NSURLRequest *req = [self imageRequestForURL:url];
   dispatch_async(q, ^{
     UIImage *image = [self imageFromRequest:req];
     [images addObject:newImage];
   }
}
dispatch_async(q, ^{
   dispatch_async(dispatch_get_main_queue(), ^{
     [self applyUpdatesForURLs:urls withImages:images];
   });
}

The queue is a standard work queue.

The applyUpdatesForURLs:withImages: block is guaranteed to run after all images have been downloaded, by virtue of being enqueued in a serial queue after the image download blocks.

There's no synchronization problems with images because all code working with it runs serially not concurrently.

And the UI update ultimately occurs on the main thread.

Upvotes: 2

jkh
jkh

Reputation: 3266

First, you can't dispatch_sync() on a concurrent queue. (Well, you can, but it's exactly the same as a dispatch_async().) dispatch_sync() only makes any kind of conceptual sense on a serial queue, where you are saying, "I want to wait until all blocks queued up before me end, then do this block, then return control to the calling thread."

Second, rdelmar's answer is correct - you are overcomplicating this. Even if you don't want to use the batch completion handler on NSURLConnection, you certainly don't need to nest two block dispatches on the concurrent queue - just one block which does the batch downloads (running async on a concurrent queue) with a nested block which does the UI update on the main queue upon completion is just fine!

Upvotes: 1

David Hoerl
David Hoerl

Reputation: 41652

You cannot have multiple blocks in a concurrent queue updating a mutable array - it just won't work properly since mutable containers are not thread safe. When the image is available queue a block on the main queue, and add it to the array there instead.

Upvotes: 1

rdelmar
rdelmar

Reputation: 104092

Your posted method looks overly complex. Why not use the NSURLConnection method, sendAsynchronousRequest:queue:completionHandler:. You can specify the mainQueue as the queue parameter to update your UI.

    NSURL *url = [NSURL URLWithString:@"your server address"];
    NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:5];
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *theData, NSError *error){
        if (error == nil) {
            //do what you want with theData and update the UI
        }
    }];

Upvotes: 1

Related Questions