简体   繁体   中英

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.

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.

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
        }
    }];

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.

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!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM