2015-09-18 15 views
16

Chcę pobrać kilka plików, na przykład 100 plików w tym samym czasie. Dlatego postanowiłem dodać wątki pobierania do kolejki wysyłkowej, a GCD dostosuje liczbę wątków uruchomionych w tym samym czasie.Oczekiwanie na wiele asynchronicznych zadań pobierania

Problem tutaj: blok w dispatch_async zostanie zakończony natychmiast, ponieważ task będzie działać na innym wątku. Tak więc, jeśli urls ma długość 100, natychmiast utworzy 100 wątków.

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

Jak mogę skonfigurować blok w dispatch_async czekać na pobieranie, aby zakończyć zadanie? Nie chcę używać dispatch_semaphore, ponieważ pozwala on tylko uruchomić jedno zadanie pobierania w tym samym czasie.

Odpowiedz

20

Aby rozwinąć na odpowiedź Abhinav jest, należy:

  1. Zastosowanie dispatch_group_create() aby utworzyć grupę.
  2. Zadzwoń pod dispatch_group_enter(group) przed rozpoczęciem każdego zadania pobierania.
  3. Zadzwoń pod dispatch_group_leave(group) wewnątrz programu obsługi zakończenia zadania.
  4. Następnie wywołaj dispatch_group_notify(group, queue, ^{ ... }), aby dodać do kolejki blok, który zostanie wykonany po ukończeniu wszystkich zadań.

Możesz zobaczyć przykład w this post.

(Nawiasem mówiąc, nie jest prawdą, że wykonanie 100 dispatch_async z rzędu spowoduje natychmiastowe utworzenie 100 wątków, ale system nadal zachowuje kontrolę nad liczbą wątków używanych do zaspokojenia kolejki, ale Twój kod nie czeka na jedno z zadań, aby zakończyć przed zwraca ani nie próbować synchronizować pomiędzy wiele zadań ukończenie.)

+1

Myślę, że to jest poprawna odpowiedź. Dodatkowo użycie osobnej kolejki do planowania zadań (takich jak 'queueDownloadTask') jest niepotrzebne, ponieważ' task.resume() 'zwraca się natychmiast. Wystarczy użyć głównej kolejki, aby je zaplanować. – Fujia

+0

Prawda. Ja też bym to polecił. – jtbandes

2

Powinieneś użyć dispatch_group_t. Proszę odnieść się do Apple documentation, aby rozwiązać problem.

+0

Nie wiem, jak zastosować metodę dispatch_group w tej sytuacji :( – t4nhpt

+2

"rozwiń"! Wspaniały! – Mundi

+0

:) ... Nie słowo słownikowe, ale brzmi dobrze, więc ... :)! – Abhinav

3

działający przykład objective-c do pobierania plików obsługujący kilka

- (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"); 
    }); 
} 
16

W Swift3,

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 
    taskGroup.notify(queue: DispatchQueue.main, work: DispatchWorkItem(block: { 
     // All tasks are done. 
     // ... 
    }) 

} 
+4

'defer {taskGroup.leave()}' jest miłym akcentem. – PDK

+0

Po prostu ciekawy, w jaki sposób "odroczyć" pomóc zrównoważyć 'leave()' i 'enter()' połączeń? Czy nie byłby zrównoważony bez "odroczenia"? – Hlung

+0

@Hlung, 'defer' to składnia' Swift'. W końcu zostanie wykonany. [I google a document] (https://www.hackingwithswift.com/new-syntax-swift-2-defer). To jest jak "try/finally". – AechoLiu

Powiązane problemy