2012-10-01 52 views
2

我试图从API下载数据,然后将其显示给用户。GCD - 具有同步任务的异步队列

下面是我在做什么:

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

我的问题是,既然newImages__block声明,我总是保证在主队列执行第二任务时拥有最新的数据newImages?如果不是,将数组内容传递到第二个块并确保其最新内容的最安全方法是什么?

我认为我的数据在某个地方变得混乱,因此我在问这个问题。

如果这看起来很好,那么我会发布我的完整代码,以帮助整个事情,以防万一其他错误。

回答

2

的溶液的草图:

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

队列是一个标准的工作队列。

applyUpdatesForURLs:withImages:块保证在下载完所有图像之后运行,这是因为在图像下载块之后被排队在串行队列中。

images没有同步问题,因为所有使用它的代码都是串行运行而不是同时运行。

UI更新最终发生在主线程上。

1

您发布的方法看起来过于复杂。为什么不使用NSURLConnection方法,sendAsynchronousRequest:queue:completionHandler :.您可以指定mainQueue作为队列参数来更新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 
     } 
    }]; 
+0

问题是我有多个图像,我想一次下载它们,然后对其执行UI更新。我正在for循环中抓取图像。您提供的代码将通过图像来完成该图像。整个批次不会更新更好吗?我实际上是通过'performBatchUpdates:completion'来更新UI的'UICollectionView',就像你在我以前的问题中看到的那样。你会建议什么?我应该用其他东西来替换'performBatchUpdates'吗?如果是这样,那么我将使用什么来在集合视图之后执行另一个更新? – darksky

+0

如果不知道下载的详细信息,我很难分辨出来,但没有理由我发布的代码不能用于批量下载。从服务器下载包含图像数组(或可以转换为数组的一些对象)?如果是这样,那么你仍然可以使用performBatchUpdates:completion:来做UI更新。 – rdelmar

1

在并发队列中不能有多个块更新可变数组 - 它只是不能正常工作,因为可变容器不是线程安全的。当图像可用时,将主队列上的块排队,然后将其添加到阵列中。

+0

尽管两个块都是同步分派的。这仍然不安全?如果没有,当图像可用时,我如何在主队列上排列一个块?你能否详细说明一下/举一个例子?我不确定我明白。 – darksky

+0

当您使用dispatch_sync时,它意味着您要等到它完成。如果您有一个运行该块的并发队列,则两个块都可以尝试写入该阵列。当你触摸数组时,它必须位于像mainQueue这样的串行队列上(但如果所有的访问都在该队列上,它可以在任何串行队列上--Mike Ash可能会在这个用法上有文章)。 –

+0

我不确定我的理解。我运行了一个与主线程相关的异步队列。但是,在该并发队列中,我运行了两个同步块,这应该保证它们将在该队列中同步运行。放置断点,我得到了这个行为,如我所料。第一个块完成执行,第二个块执行更新UI。但我相信那里可能会有一些腐败的数据。但查询数组看起来没问题。 – darksky

1

首先,您不能在并发队列上使用dispatch_sync()。 (嗯,你可以,但它与dispatch_async()完全一样。)dispatch_sync()只对串行队列有任何概念意义,你在这里说:“我想等到所有块在我之前排队,然后做这个块,然后将控制权返回给调用线程。“

第二,rdelmar's answer是正确的 - 你是过度复杂。即使您不想在NSURLConnection上使用批量完成处理程序,您当然也不需要在并发队列上嵌套两个块分派 - 只需一个批量下载(在并发队列上运行异步)的块在完成时在主队列上进行UI更新的嵌套块非常好!