Chờ cho đến khi nhiệm vụ kết thúc


99

Làm cách nào tôi có thể đặt mã của mình đợi cho đến khi tác vụ trong DispatchQueue kết thúc? Nó có cần bất kỳ CompletionHandler hay gì đó không?

func myFunction() {
    var a: Int?

    DispatchQueue.main.async {
        var b: Int = 3
        a = b
    }

    // wait until the task finishes, then print 

    print(a) // - this will contain nil, of course, because it
             // will execute before the code above

}

Tôi đang sử dụng Xcode 8.2 và viết bằng Swift 3.

Câu trả lời:


228

Sử dụng DispatchGroups để đạt được điều này. Bạn có thể nhận được thông báo khi cuộc gọi enter()và nhóm leave()được cân bằng:

func myFunction() {
    var a: Int?

    let group = DispatchGroup()
    group.enter()

    DispatchQueue.main.async {
        a = 1
        group.leave()
    }

    // does not wait. But the code in notify() gets run 
    // after enter() and leave() calls are balanced

    group.notify(queue: .main) {
        print(a)
    }
}

hoặc bạn có thể đợi:

func myFunction() {
    var a: Int?

    let group = DispatchGroup()
    group.enter()

    // avoid deadlocks by not using .main queue here
    DispatchQueue.global(attributes: .qosDefault).async {
        a = 1
        group.leave()
    }

    // wait ...
    group.wait()

    print(a) // you could also `return a` here
}

Lưu ý : group.wait()chặn hàng đợi hiện tại (có thể là hàng đợi chính trong trường hợp của bạn), vì vậy bạn phải dispatch.asyncở hàng đợi khác (như trong mã mẫu ở trên) để tránh bế tắc .


Tôi muốn thực thi một hàm trong một lớp khác nhưng tôi muốn đợi để hoàn thành hàm đó và sau đó trong lớp hiện tại tiếp tục, làm thế nào tôi có thể xử lý điều đó?
Saeed Rahmatolahi

2
@SaeedRahmatolahi: Sử dụng waitphương pháp này (nếu bạn chặn không có vấn đề gì, tức là nếu bạn không ở trên luồng chính) hoặc cung cấp trình xử lý hoàn thành hoặc sử dụng phương pháp thông báo trong lớp gọi của bạn.
cạnThought

Tại sao bạn gọi group.enterbên ngoài khối không đồng bộ? Không phải là trách nhiệm của mỗi khối khi vào và rời nhóm?
Hóa đơn

4
@Bill waitđợi cho đến khi enterleavecác cuộc gọi được cân bằng. Nếu bạn đặt enterlệnh đóng, waitsẽ không đợi vì entervẫn chưa được gọi và do đó số lượng enterleavecuộc gọi được cân bằng (# enter == 0, # leav == 0).
cạnThought

1
@rustyMagnet đối với các thử nghiệm, đây có lẽ không phải là cách tốt nhất. Sử dụng XCTTestExpectations thay thế. Xem mã mẫu này
nôngThought

26

Trong Swift 3, không cần trình xử lý hoàn thành khi DispatchQueuehoàn thành một tác vụ. Hơn nữa, bạn có thể đạt được mục tiêu của mình theo nhiều cách khác nhau

Một cách là:

    var a: Int?

    let queue = DispatchQueue(label: "com.app.queue")
    queue.sync {

        for  i in 0..<10 {

            print("Ⓜ️" , i)
            a = i
        }
    }

    print("After Queue \(a)")

Nó sẽ đợi cho đến khi vòng lặp kết thúc nhưng trong trường hợp này luồng chính của bạn sẽ bị chặn.

Bạn cũng có thể làm điều tương tự như sau:

    let myGroup = DispatchGroup()
    myGroup.enter()
    //// Do your task

    myGroup.leave() //// When your task completes
     myGroup.notify(queue: DispatchQueue.main) {

        ////// do your remaining work
    }

Một điều cuối cùng: Nếu bạn muốn sử dụng CompleteHandler khi nhiệm vụ của bạn hoàn thành bằng cách sử dụng DispatchQueue, bạn có thể sử dụng DispatchWorkItem.

Đây là một ví dụ về cách sử dụng DispatchWorkItem:

let workItem = DispatchWorkItem {
    // Do something
}

let queue = DispatchQueue.global()
queue.async {
    workItem.perform()
}
workItem.notify(queue: DispatchQueue.main) {
    // Here you can notify you Main thread
}

1
Tôi đã cố gắng tất cả chúng và không ai làm việc trong khi cố gắng để xử lý các cuộc gọi căn cứ hỏa lực trong một vòng lặp for

2

Sử dụng nhóm điều phối

   dispatchGroup.enter()
   FirstOperation(completion: { _ in
dispatchGroup.leave()
  })
    dispatchGroup.enter()
    SecondOperation(completion: { _ in
dispatchGroup.leave()
  })
   dispatchGroup.wait() //Waits here on this thread until the two operations complete executing.

5
Giả sử bạn gọi điều này trên hàng đợi chính, điều này sẽ gây ra bế tắc.
cạnThought

@shallowThought Đúng vậy.
Prateekro

Tôi muốn thực thi một hàm trong một lớp khác nhưng tôi muốn đợi để hoàn thành hàm đó và sau đó trong lớp hiện tại tiếp tục, làm thế nào tôi có thể xử lý điều đó?
Saeed Rahmatolahi

1
Mặc dù ví dụ trên sẽ chặn trên chuỗi chính, như các câu trả lời trước đây đã chỉ ra, gói bên trong DispatchQueue.global().async{}sẽ không chặn hàng đợi chính.
JonnyB

2

Phiên bản Swift 5 của giải pháp

func myCriticalFunction () {var value1: Chuỗi? var value2: Chuỗi?

let group = DispatchGroup()


group.enter()
//async operation 1
DispatchQueue.global(qos: .default).async { 
    // Network calls or some other async task
    value1 = //out of async task
    group.leave()
}


group.enter()
//async operation 2
DispatchQueue.global(qos: .default).async {
    // Network calls or some other async task
    value2 = //out of async task
    group.leave()
}


group.wait()

print("Value1 \(value1) , Value2 \(value2)") 

}


1

Swift 4

Bạn có thể sử dụng Hàm Async cho những trường hợp này. Khi bạn sử dụng DispatchGroup(), đôi khi có thể xảy ra bế tắc .

var a: Int?
@objc func myFunction(completion:@escaping (Bool) -> () ) {

    DispatchQueue.main.async {
        let b: Int = 3
        a = b
        completion(true)
    }

}

override func viewDidLoad() {
    super.viewDidLoad()

    myFunction { (status) in
        if status {
            print(self.a!)
        }
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.