等待多个异步下载任务完成

31

我想同时下载一些文件,比如100个。所以我决定将我的下载线程添加到调度队列中,GCD会调整同时运行的线程数。

问题在于:在dispatch_async的块被立即完成,因为task会在另一个线程上运行。所以,如果urls的长度是100,它会立即创建100个线程。

var queueDownloadTask = dispatch_queue_create("downloadQueue", nil)

for url in urls {
    dispatch_async(queueDownloadTask) {

        let config = NSURLSessionConfiguration.defaultSessionConfiguration()
        let fileTransferSession = NSURLSession(configuration: config)

        let task = fileTransferSession.downloadTaskWithURL(url, completionHandler: { (responseUrl, response, error) -> Void in
            println("completed")
        })
        task.resume()
    }
}

如何配置 dispatch_async 中的块,以等待下载任务完成?我不想使用 dispatch_semaphore,因为它只允许同时运行一个下载任务。


可能是等待多个块完成的重复问题。 - pkamb
5个回答

48

在Swift 4中,

func executeMultiTask() {
     //1. Create group
     let taskGroup = DispatchGroup()

     //2. Enter group
     taskGroup.enter()
     myTask1.execute(completeHandler: {
         // ...
         //3. Leave group
         taskGroup.leave() //< balance with taskGroup.enter()
     })

     /* Add more tasks ...
     //2. Enter group
     taskGroup.enter()
     myTask2.execute(completeHandler: {
         //3. Leave group
         defer {
            // Use `defer` to make sure, `leave()` calls are balanced with `enter()`.
            taskGroup.leave() 
         }
         // ... more
     })
     */

     //4. Notify when all task completed at main thread queue.
     taskGroup.notify(queue: .main) { 
         // All tasks are done.
         // ...   
     }

}

只是好奇,defer 如何帮助平衡 leave()enter() 的调用?不用 defer 也不是同样平衡吗? - Hlung
@Hlung,deferSwift语法。它将在最后执行。我谷歌了一个文档。它就像try/finally - AechoLiu
1
如果您在作用域中有多个退出条件,那么defer会很方便。但是如果没有的话,我认为不需要使用defer。我们可以在作用域的结尾处直接放置 taskGroup.leave() - Hlung
2
我认为这是一个习惯问题。当我写 taskGroup.enter() 时,我在接下来的23行中加入了 taskGroup.leave()。如果子程序有2030行长,很难追踪 enter()leave() 是否平衡。 - AechoLiu
由于某些原因,在iOS 11中这并不总是有效。提前告知。 - Jonny

35

补充Abhinav的回答,您应该:

  1. 使用dispatch_group_create()创建一个组。
  2. 在开始每个下载任务之前调用dispatch_group_enter(group)
  3. 在任务的完成处理程序中调用dispatch_group_leave(group)
  4. 然后调用dispatch_group_notify(group, queue, ^{ ... })来排队执行一个块,当所有任务都完成时将执行此块。

您可以在这篇文章中看到一个示例。

(顺便说一句,连续做100个dispatch_async不会立即创建100个线程。系统仍然控制着使用多少个线程来满足队列。但是,您的代码在返回之前不会等待任何任务完成,也不会尝试在多个任务完成之间进行同步。)


1
我认为这是一个有效的答案。此外,使用单独的队列来调度任务(如queueDownloadTask)是不必要的,因为task.resume()会立即返回。只需使用主队列来调度它们就足够了。 - Fujia
真的。我也会推荐那样做。 - jtbandes

5

一个可用的Objective-C示例,用于下载多个文件

- (void) downloadFiles: (NSMutableArray *) fileArray : (NSString *)destParentDir{
    dispatch_group_t serviceGroup = dispatch_group_create();

    for (id fileInfo in fileArray) {
        dispatch_group_enter(serviceGroup);

        NSFileManager *fileManager = [NSFileManager defaultManager];
        NSString *fileName = [fileInfo valueForKey:@"name"];

        //Create SubDirs if needed, note that you need to exclude file name for the dirsString :)
        //[fileManager createDirectoryAtPath:dirsString withIntermediateDirectories:true attributes:nil error:NULL];

        //Download file
        NSURL  *url = [NSURL URLWithString:@"YOUR_FILE_URL"];
        NSData *urlData = [NSData dataWithContentsOfURL:url];
        if(urlData)
        {
            NSString  *localPath = [NSString stringWithFormat:@"%@/%@", destParentDir, fileName];
            [urlData writeToFile:localPath atomically:YES];
        }
        dispatch_group_leave(serviceGroup);
    }

    dispatch_group_notify(serviceGroup, dispatch_get_main_queue(),^{
        NSLog(@"Complete files download");
    });
}

4
你应该使用dispatch_group_t。请参考Apple文档来解决你的问题。

我不知道如何将dispatch_group应用于这种情况 :( - t4nhpt
4
"解决方案化"!太棒了! - Mundi

0
创建一个 DispatchGroup。将每个下载都包装在 enterleave 中。提供一个回调函数给 notify,当所有配对完成时将被调用。
var dispatchGroup = DispatchGroup()

for url in urls {
    // ... Set up the download
    dispatchGroup.enter()
    let task = fileTransferSession.downloadTaskWithURL(url) { (responseUrl, response, error) -> Void in
        println("Individual download complete.")
        dispatchGroup.leave()
     })
    task.resume()

    dispatchGroup.notify(queue: .main) {
        println("All downloads complete.")
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接