รอจนกว่าการวนซ้ำอย่างรวดเร็วด้วยการร้องขอเครือข่ายแบบอะซิงโครนัสเสร็จสิ้นการดำเนินการ


159

ฉันต้องการสำหรับ in ในการส่งเครือข่ายจำนวนมากขอไปยังฐานข้อมูลแล้วส่งข้อมูลไปยังตัวควบคุมมุมมองใหม่เมื่อวิธีการเสร็จสิ้นการดำเนินการ นี่คือรหัสของฉัน:

var datesArray = [String: AnyObject]()

for key in locationsArray {       
    let ref = Firebase(url: "http://myfirebase.com/" + "\(key.0)")
    ref.observeSingleEventOfType(.Value, withBlock: { snapshot in

        datesArray["\(key.0)"] = snapshot.value
    })
}
// Segue to new view controller here and pass datesArray once it is complete 

ฉันมีข้อกังวลสองสามข้อ ก่อนอื่นฉันจะรอจนกว่าการวนซ้ำจะเสร็จสิ้นและคำขอเครือข่ายทั้งหมดเสร็จสมบูรณ์ได้อย่างไร ฉันไม่สามารถแก้ไขฟังก์ชัน observSingleEventOfType มันเป็นส่วนหนึ่งของ firebase SDK นอกจากนี้ฉันจะสร้างเงื่อนไขการแข่งขันบางประเภทโดยพยายามเข้าถึง dateArray จากการวนซ้ำที่แตกต่างกันของ for for loop (หวังว่าจะสมเหตุสมผล) ฉันได้อ่านเกี่ยวกับ GCD และ NSOperation แล้ว แต่ฉันก็หลงทางนิดหน่อยเพราะนี่เป็นแอพแรกที่ฉันสร้างขึ้น

หมายเหตุ: สถานที่ตั้งเป็นอาร์เรย์ที่มีคีย์ที่ฉันต้องการเข้าถึงใน firebase นอกจากนี้สิ่งสำคัญคือการร้องขอเครือข่ายจะถูกปิดแบบอะซิงโครนัส ฉันแค่ต้องการรอจนกระทั่งคำขอแบบอะซิงโครนัสทั้งหมดเสร็จสมบูรณ์ก่อนที่ฉันจะผ่าน dateArray ไปยังตัวควบคุมมุมมองถัดไป

คำตอบ:


338

คุณสามารถใช้กลุ่มการจัดส่งเพื่อเริ่มการโทรกลับแบบอะซิงโครนัสเมื่อคำขอทั้งหมดของคุณเสร็จสิ้น

นี่คือตัวอย่างการใช้กลุ่มการแจกจ่ายเพื่อดำเนินการติดต่อกลับแบบอะซิงโครนัสเมื่อคำขอเครือข่ายหลายคำขอเสร็จสิ้นแล้ว

override func viewDidLoad() {
    super.viewDidLoad()

    let myGroup = DispatchGroup()

    for i in 0 ..< 5 {
        myGroup.enter()

        Alamofire.request("https://httpbin.org/get", parameters: ["foo": "bar"]).responseJSON { response in
            print("Finished request \(i)")
            myGroup.leave()
        }
    }

    myGroup.notify(queue: .main) {
        print("Finished all requests.")
    }
}

เอาท์พุต

Finished request 1
Finished request 0
Finished request 2
Finished request 3
Finished request 4
Finished all requests.

มันใช้งานได้ดีมาก! ขอบคุณ! คุณมีความคิดใด ๆ ไหมถ้าฉันจะเจอกับสภาพการแข่งขันใด ๆ เมื่อฉันพยายามอัพเดทเดทอาเรย์?
Josh

ฉันไม่คิดว่าจะมีสภาพการแข่งขันที่นี่เพราะคำขอทั้งหมดเพิ่มคุณค่าให้กับการdatesArrayใช้คีย์ที่แตกต่างกัน
paulvs

1
@Josh เกี่ยวกับสภาพการแข่งขัน: สภาพการแข่งขันจะเกิดขึ้นหากตำแหน่งหน่วยความจำเดียวกันนั้นสามารถเข้าถึงได้จากเธรดที่แตกต่างกันโดยที่การเข้าถึงอย่างน้อยหนึ่งรายการเป็นการเขียน - โดยไม่ต้องใช้การซิงโครไนซ์ การเข้าถึงทั้งหมดภายในคิวการจัดส่งอนุกรมเดียวกันจะถูกซิงโครไนซ์ การซิงโครไนซ์ยังเกิดขึ้นกับการดำเนินการของหน่วยความจำที่เกิดขึ้นในคิวการจัดส่ง A ซึ่งส่งไปยังคิวการจัดส่งอื่น B ​​การดำเนินการทั้งหมดในคิว A จะถูกซิงโครไนซ์ในคิว B ดังนั้นถ้าคุณดูที่โซลูชัน ;)
CouchDeveloper

@ จอชโปรดระวังว่า "การเขียนโปรแกรมสนามแข่ง" เป็นคำที่ยากอย่างน่าทึ่ง ไม่สามารถพูดได้ทันทีว่า "คุณทำ / ไม่มีปัญหา" สำหรับโปรแกรมเมอร์มือสมัครเล่น: "เพียงแค่" มักจะทำงานในลักษณะที่หมายความว่าปัญหาสนามแข่งเป็นเพียงเป็นไปไม่ได้ (ตัวอย่างเช่นสิ่งต่าง ๆ เช่น "ทำสิ่งเดียวในครั้งเดียว" เป็นต้น) แม้แต่การทำเช่นนั้นก็เป็นความท้าทายอย่างมากในการเขียนโปรแกรม
Fattie

เด็ดสุด ๆ แต่ฉันมีคำถาม สมมติว่าคำขอ 3 และคำขอ 4 ล้มเหลว (เช่นข้อผิดพลาดเซิร์ฟเวอร์ข้อผิดพลาดการอนุญาตอะไร) แล้วจะเรียกลูปอีกครั้งสำหรับคำขอที่เหลือเพียงอย่างเดียวได้อย่างไร (คำขอ 3 & คำขอ 4)
JD

43

Xcode 8.3.1 - Swift 3

นี่คือคำตอบที่ยอมรับของ paulvs แปลงเป็น Swift 3:

let myGroup = DispatchGroup()

override func viewDidLoad() {
    super.viewDidLoad()

    for i in 0 ..< 5 {
        myGroup.enter()
        Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"]).responseJSON { response in
            print("Finished request \(i)")
            myGroup.leave()
        }
    }

    myGroup.notify(queue: DispatchQueue.main, execute: {
        print("Finished all requests.")
    })
}

1
สวัสดีมันใช้งานได้กับ 100 คำขอหรือไม่ หรือ 1,000 เนื่องจากฉันกำลังพยายามทำสิ่งนี้กับคำขอประมาณ 100 รายการและขัดข้องในการดำเนินการตามคำขอให้เสร็จสมบูรณ์
lopes710

ฉันที่สอง @ lopes710-- ดูเหมือนว่าจะอนุญาตให้คำขอทั้งหมดทำงานแบบขนานใช่ไหม
คริสเจ้าชาย

หากฉันมีคำขอเครือข่าย 2 คำขอคำขอซ้อนกันอีกรายการภายในสำหรับลูปดังนั้นวิธีการตรวจสอบให้แน่ใจว่าสำหรับการวนซ้ำแต่ละครั้งสำหรับลูปคำขอทั้งสองเสร็จสมบูรณ์ ?
Awais Fayyaz

@ ช่องโปรดมีวิธีที่ฉันจะได้รับคำสั่งนี้หรือไม่?
อิสราเอล Meshileya

41

Swift 3 หรือ 4

หากคุณไม่สนใจคำสั่งซื้อให้ใช้คำตอบของ @ paulvs มันทำงานได้อย่างสมบูรณ์

ในกรณีที่ใครต้องการได้ผลลัพธ์ตามลำดับแทนที่จะยิงพวกเขาพร้อมกัน นี่คือรหัส

let dispatchGroup = DispatchGroup()
let dispatchQueue = DispatchQueue(label: "any-label-name")
let dispatchSemaphore = DispatchSemaphore(value: 0)

dispatchQueue.async {

    // use array categories as an example.
    for c in self.categories {

        if let id = c.categoryId {

            dispatchGroup.enter()

            self.downloadProductsByCategory(categoryId: id) { success, data in

                if success, let products = data {

                    self.products.append(products)
                }

                dispatchSemaphore.signal()
                dispatchGroup.leave()
            }

            dispatchSemaphore.wait()
        }
    }
}

dispatchGroup.notify(queue: dispatchQueue) {

    DispatchQueue.main.async {

        self.refreshOrderTable { _ in

            self.productCollectionView.reloadData()
        }
    }
}

แอพของฉันต้องส่งไฟล์หลายไฟล์ไปยังเซิร์ฟเวอร์ FTP ซึ่งรวมถึงการเข้าสู่ระบบก่อน วิธีการนี้รับประกันว่าแอปจะลงชื่อเข้าใช้เพียงครั้งเดียว (ก่อนที่จะอัปโหลดไฟล์แรก) แทนที่จะพยายามทำหลาย ๆ ครั้งในเวลาเดียวกันโดยทั่วไปในเวลาเดียวกัน ขอบคุณ!
Neph

ฉันมีคำถามหนึ่งข้อ: มันสำคัญไหมถ้าคุณทำdispatchSemaphore.signal()ก่อนหรือหลังจากไปdispatchGroup? คุณคิดว่าจะเป็นการดีที่สุดที่จะปลดล็อคเซมาฟอร์ให้เร็วที่สุดเท่าที่จะเป็นไปได้ แต่ฉันไม่แน่ใจว่าจะเลิกกลุ่มดังกล่าวได้อย่างไรและอย่างไร ฉันทดสอบทั้งคำสั่งซื้อและดูเหมือนว่าจะไม่ได้สร้างความแตกต่าง
Neph

16

รายละเอียด

  • Xcode 10.2.1 (10E1001), Swift 5

สารละลาย

import Foundation

class SimultaneousOperationsQueue {
    typealias CompleteClosure = ()->()

    private let dispatchQueue: DispatchQueue
    private lazy var tasksCompletionQueue = DispatchQueue.main
    private let semaphore: DispatchSemaphore
    var whenCompleteAll: (()->())?
    private lazy var numberOfPendingActionsSemaphore = DispatchSemaphore(value: 1)
    private lazy var _numberOfPendingActions = 0

    var numberOfPendingTasks: Int {
        get {
            numberOfPendingActionsSemaphore.wait()
            defer { numberOfPendingActionsSemaphore.signal() }
            return _numberOfPendingActions
        }
        set(value) {
            numberOfPendingActionsSemaphore.wait()
            defer { numberOfPendingActionsSemaphore.signal() }
            _numberOfPendingActions = value
        }
    }

    init(numberOfSimultaneousActions: Int, dispatchQueueLabel: String) {
        dispatchQueue = DispatchQueue(label: dispatchQueueLabel)
        semaphore = DispatchSemaphore(value: numberOfSimultaneousActions)
    }

    func run(closure: ((@escaping CompleteClosure) -> Void)?) {
        numberOfPendingTasks += 1
        dispatchQueue.async { [weak self] in
            guard   let self = self,
                    let closure = closure else { return }
            self.semaphore.wait()
            closure {
                defer { self.semaphore.signal() }
                self.numberOfPendingTasks -= 1
                if self.numberOfPendingTasks == 0, let closure = self.whenCompleteAll {
                    self.tasksCompletionQueue.async { closure() }
                }
            }
        }
    }

    func run(closure: (() -> Void)?) {
        numberOfPendingTasks += 1
        dispatchQueue.async { [weak self] in
            guard   let self = self,
                    let closure = closure else { return }
            self.semaphore.wait(); defer { self.semaphore.signal() }
            closure()
            self.numberOfPendingTasks -= 1
            if self.numberOfPendingTasks == 0, let closure = self.whenCompleteAll {
                self.tasksCompletionQueue.async { closure() }
            }
        }
    }
}

การใช้

let queue = SimultaneousOperationsQueue(numberOfSimultaneousActions: 1, dispatchQueueLabel: "AnyString")
queue.whenCompleteAll = { print("All Done") }

 // add task with sync/async code
queue.run { completeClosure in
    // your code here...

    // Make signal that this closure finished
    completeClosure()
}

 // add task only with sync code
queue.run {
    // your code here...
}

ตัวอย่างเต็ม

import UIKit

class ViewController: UIViewController {

    private lazy var queue = { SimultaneousOperationsQueue(numberOfSimultaneousActions: 1,
                                                           dispatchQueueLabel: "AnyString") }()
    private weak var button: UIButton!
    private weak var label: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()
        let button = UIButton(frame: CGRect(x: 50, y: 80, width: 100, height: 100))
        button.setTitleColor(.blue, for: .normal)
        button.titleLabel?.numberOfLines = 0
        view.addSubview(button)
        self.button = button

        let label = UILabel(frame: CGRect(x: 180, y: 50, width: 100, height: 100))
        label.text = ""
        label.numberOfLines = 0
        label.textAlignment = .natural
        view.addSubview(label)
        self.label = label

        queue.whenCompleteAll = { [weak self] in self?.label.text = "All tasks completed" }

        //sample1()
        sample2()
    }

    func sample1() {
        button.setTitle("Run 2 task", for: .normal)
        button.addTarget(self, action: #selector(sample1Action), for: .touchUpInside)
    }

    func sample2() {
        button.setTitle("Run 10 tasks", for: .normal)
        button.addTarget(self, action: #selector(sample2Action), for: .touchUpInside)
    }

    private func add2Tasks() {
        queue.run { completeTask in
            DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + .seconds(1)) {
                DispatchQueue.main.async { [weak self] in
                    guard let self = self else { return }
                    self.label.text = "pending tasks \(self.queue.numberOfPendingTasks)"
                }
                completeTask()
            }
        }
        queue.run {
            sleep(1)
            DispatchQueue.main.async { [weak self] in
                guard let self = self else { return }
                self.label.text = "pending tasks \(self.queue.numberOfPendingTasks)"
            }
        }
    }

    @objc func sample1Action() {
        label.text = "pending tasks \(queue.numberOfPendingTasks)"
        add2Tasks()
    }

    @objc func sample2Action() {
        label.text = "pending tasks \(queue.numberOfPendingTasks)"
        for _ in 0..<5 { add2Tasks() }
    }
}

5

คุณจะต้องใช้เซมาฟอร์เพื่อจุดประสงค์นี้

 //Create the semaphore with count equal to the number of requests that will be made.
let semaphore = dispatch_semaphore_create(locationsArray.count)

        for key in locationsArray {       
            let ref = Firebase(url: "http://myfirebase.com/" + "\(key.0)")
            ref.observeSingleEventOfType(.Value, withBlock: { snapshot in

                datesArray["\(key.0)"] = snapshot.value

               //For each request completed, signal the semaphore
               dispatch_semaphore_signal(semaphore)


            })
        }

       //Wait on the semaphore until all requests are completed
      let timeoutLengthInNanoSeconds: Int64 = 10000000000  //Adjust the timeout to suit your case
      let timeout = dispatch_time(DISPATCH_TIME_NOW, timeoutLengthInNanoSeconds)

      dispatch_semaphore_wait(semaphore, timeout)

     //When you reach here all request would have been completed or timeout would have occurred.

3

Swift 3: คุณสามารถใช้ semaphores ด้วยวิธีนี้ มันมีประโยชน์มากนอกจากคุณจะสามารถติดตามได้อย่างแม่นยำว่ากระบวนการใดเสร็จสมบูรณ์เมื่อใด สิ่งนี้ถูกดึงมาจากรหัสของฉัน:

    //You have to create your own queue or if you need the Default queue
    let persons = persistentContainer.viewContext.persons
    print("How many persons on database: \(persons.count())")
    let numberOfPersons = persons.count()

    for eachPerson in persons{
        queuePersonDetail.async {
            self.getPersonDetailAndSave(personId: eachPerson.personId){person2, error in
                print("Person detail: \(person2?.fullName)")
                //When we get the completionHandler we send the signal
                semaphorePersonDetailAndSave.signal()
            }
        }
    }

    //Here we will wait
    for i in 0..<numberOfPersons{
        semaphorePersonDetailAndSave.wait()
        NSLog("\(i + 1)/\(persons.count()) completed")
    }
    //And here the flow continues...

1

เราสามารถทำได้ด้วยการสอบถามซ้ำ รับแนวคิดจากโค้ดด้านล่าง:

var count = 0

func uploadImages(){

    if count < viewModel.uploadImageModelArray.count {
        let item = viewModel.uploadImageModelArray[count]
        self.viewModel.uploadImageExpense(filePath: item.imagePath, docType: "image/png", fileName: item.fileName ?? "", title: item.imageName ?? "", notes: item.notes ?? "", location: item.location ?? "") { (status) in

            if status ?? false {
                // successfully uploaded
            }else{
                // failed
            }
            self.count += 1
            self.uploadImages()
        }
    }
}

-1

กลุ่มการจัดส่งนั้นดี แต่ลำดับของการร้องขอที่ส่งเป็นแบบสุ่ม

Finished request 1
Finished request 0
Finished request 2

ในกรณีโครงการของฉันคำขอที่จำเป็นต้องเปิดตัวแต่ละครั้งเป็นลำดับที่ถูกต้อง หากสามารถช่วยคนได้:

public class RequestItem: NSObject {
    public var urlToCall: String = ""
    public var method: HTTPMethod = .get
    public var params: [String: String] = [:]
    public var headers: [String: String] = [:]
}


public func trySendRequestsNotSent (trySendRequestsNotSentCompletionHandler: @escaping ([Error]) -> () = { _ in }) {

    // If there is requests
    if !requestItemsToSend.isEmpty {
        let requestItemsToSendCopy = requestItemsToSend

        NSLog("Send list started")
        launchRequestsInOrder(requestItemsToSendCopy, 0, [], launchRequestsInOrderCompletionBlock: { index, errors in
            trySendRequestsNotSentCompletionHandler(errors)
        })
    }
    else {
        trySendRequestsNotSentCompletionHandler([])
    }
}

private func launchRequestsInOrder (_ requestItemsToSend: [RequestItem], _ index: Int, _ errors: [Error], launchRequestsInOrderCompletionBlock: @escaping (_ index: Int, _ errors: [Error] ) -> Void) {

    executeRequest(requestItemsToSend, index, errors, executeRequestCompletionBlock: { currentIndex, errors in
        if currentIndex < requestItemsToSend.count {
            // We didn't reach last request, launch next request
            self.launchRequestsInOrder(requestItemsToSend, currentIndex, errors, launchRequestsInOrderCompletionBlock: { index, errors in

                launchRequestsInOrderCompletionBlock(currentIndex, errors)
            })
        }
        else {
            // We parse and send all requests
            NSLog("Send list finished")
            launchRequestsInOrderCompletionBlock(currentIndex, errors)
        }
    })
}

private func executeRequest (_ requestItemsToSend: [RequestItem], _ index: Int, _ errors: [Error], executeRequestCompletionBlock: @escaping (_ index: Int, _ errors: [Error]) -> Void) {
    NSLog("Send request %d", index)
    Alamofire.request(requestItemsToSend[index].urlToCall, method: requestItemsToSend[index].method, parameters: requestItemsToSend[index].params, headers: requestItemsToSend[index].headers).responseJSON { response in

        var errors: [Error] = errors
        switch response.result {
        case .success:
            // Request sended successfully, we can remove it from not sended request array
            self.requestItemsToSend.remove(at: index)
            break
        case .failure:
            // Still not send we append arror
            errors.append(response.result.error!)
            break
        }
        NSLog("Receive request %d", index)
        executeRequestCompletionBlock(index+1, errors)
    }
}

โทร :

trySendRequestsNotSent()

ผลลัพธ์ :

Send list started
Send request 0
Receive request 0
Send request 1
Receive request 1
Send request 2
Receive request 2
...
Send list finished

ดูข่าวสารเพิ่มเติม: สรุปสาระสำคัญ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.