         }
     }
     
-    func reportProgress() {
-        progressSourceBlock?(skippedSources, failedSources, completedSources)
-        progressBlock?(
-            skippedSources.compactMap { $0.asResource },
-            failedSources.compactMap { $0.asResource },
-            completedSources.compactMap { $0.asResource }
-        )
-    }
-    
-    func reportCompletionOrStartNext() {
-        CallbackQueue.mainAsync.execute {
-            if let resource = self.pendingSources.popFirst() {
-                self.startPrefetching(resource)
-            } else {
-                guard self.tasks.isEmpty else { return }
-                self.handleComplete()
-            }
-        }
-    }
-    
-    func handleComplete() {
+    private func reportProgress() {
+        CallbackQueue.mainCurrentOrAsync.execute {
+            self.progressSourceBlock?(self.skippedSources, self.failedSources, self.completedSources)
+            self.progressBlock?(
+                self.skippedSources.compactMap { $0.asResource },
+                self.failedSources.compactMap { $0.asResource },
+                self.completedSources.compactMap { $0.asResource }
+            )
+        }
+    }
+    
+    private func reportCompletionOrStartNext() {
+        if let resource = self.pendingSources.popFirst() {
+            // Loose call stack for huge ammount of sources.
+            pretchQueue.async { self.startPrefetching(resource) }
+        } else {
+            guard allFinished else { return }
+            self.handleComplete()
+        }
+    }
+
+    var allFinished: Bool {
+        return skippedSources.count + failedSources.count + completedSources.count == prefetchSources.count
+    }
+    
+    private func handleComplete() {
         // The completion handler should be called on the main thread
         CallbackQueue.mainCurrentOrAsync.execute {
             self.completionSourceHandler?(self.skippedSources, self.failedSources, self.completedSources)

        }
    }
    
    func append(cached source: Source) {
        skippedSources.append(source)
 
        reportProgress()
        reportCompletionOrStartNext()
    }
    
    func startPrefetching(_ source: Source)
    {
        if optionsInfo.forceRefresh {
            downloadAndCache(source)

    /// of assets that are required for later use in an app. This code will not try and update any UI
    /// with the results of the process.
    public func start() {
        pretchQueue.async {
            guard !self.stopped else {
                assertionFailure("You can not restart the same prefetcher. Try to create a new prefetcher.")
                self.handleComplete()
                return
            }

            guard self.maxConcurrentDownloads > 0 else {
                assertionFailure("There should be concurrent downloads value should be at least 1.")
                self.handleComplete()
                return
            }

            // Empty case.
            guard self.prefetchSources.count > 0 else {
                self.handleComplete()
                return
            }

            let initialConcurrentDownloads = min(self.prefetchSources.count, self.maxConcurrentDownloads)
            for _ in 0 ..< initialConcurrentDownloads {
                if let resource = self.pendingSources.popFirst() {
                    self.startPrefetching(resource)
                }
            }
        }
    }

    /// Stops current downloading progress, and cancel any future prefetching activity that might be occuring.
    public func stop() {
        pretchQueue.async {
            if self.finished { return }
            self.stopped = true
            self.tasks.values.forEach { $0.cancel() }
        }
    }
    
    private func downloadAndCache(_ source: Source) {

        let downloadTaskCompletionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void) = { result in
            self.tasks.removeValue(forKey: source.cacheKey)
