Чекаємо, поки завдання закінчиться


99

Як я можу змусити свій код чекати, поки завдання в DispatchQueue закінчиться? Чи потрібен якийсь CompletionHandler чи щось таке?

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

}

Я використовую Xcode 8.2 і пишу в Swift 3.

Відповіді:


229

Використовуйте DispatchGroups для досягнення цього. Ви можете отримувати сповіщення, коли група enter()та leave()дзвінки збалансовані:

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

або ви можете зачекати:

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
}

Примітка : group.wait()блокує поточну чергу (напевно, основну чергу у вашому випадку), тому вам доведеться перейти dispatch.asyncдо іншої черги (як, наприклад, у наведеному вище зразку коду), щоб уникнути тупикової ситуації .


Я хочу виконати функцію в іншому класі, але я хочу зачекати, щоб закінчити цю функцію, а потім у поточному класі продовжувати, як я можу це впоратися?
Саїд Рахматолахі

2
@SaeedRahmatolahi: або скористайтеся waitпідходом (якщо для вас це не проблема для блокування, тобто якщо ви не в основному потоці) або надайте обробник завершення або використовуйте підхід сповіщення у вашому класі виклику.
неглибокоПодумав

Чому ви телефонуєте group.enterпоза блоком асинхронізації? Чи не повинно бути відповідальність кожного блоку входити та виходити з групи?
Білл

4
@Bill waitчекає , поки enterта leaveвиклики збалансовані. Якщо ви поклали enterв закритті, waitне чекати б тому , що enterще не було , і , таким чином , називається число enterі leaveвиклики будуть збалансовані (# введіть == 0, # leav == 0).
неглибокаДослід

1
@rustyMagnet для тестів це, мабуть, не шлях. Використовуйте XCTTestExpectationзамість s. Дивіться цей зразок коду
неглибокоЗмінено

26

У Swift 3 немає необхідності в DispatchQueueобробці завершення, коли закінчується одне завдання. Крім того, ви можете досягти своєї мети різними способами

Один із способів такий:

    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)")

Він зачекає, поки цикл закінчиться, але в цьому випадку ваша основна нитка блокується.

Ви також можете зробити те ж саме, що і це:

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

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

        ////// do your remaining work
    }

Останнє: якщо ви хочете скористатися програмою завершення, коли ваше завдання завершиться за допомогою DispatchQueue, ви можете використовувати DispatchWorkItem.

Ось приклад використання 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
Я спробував їх усіх, і жоден не спрацював, намагаючись обробити дзвінки Firebase у циклі for

2

Використовуйте диспетчерську групу

   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
Якщо припустити, що ви викликаєте це в основній черзі, це призведе до тупикової ситуації.
неглибокаДоля

@shallowThought Так правда.
Prateekro

Я хочу виконати функцію в іншому класі, але я хочу зачекати, щоб закінчити цю функцію, а потім у поточному класі продовжувати, як я можу це впоратися?
Саїд Рахматолахі

1
Хоча вищенаведений приклад блокується в основній темі, як показали попередні відповіді, обгортання всередині DispatchQueue.global().async{}не блокує основну чергу.
JonnyB

2

Швидка версія 5 рішення

func myCriticalFunction () {var value1: Рядок? var value2: Рядок?

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

Швидкий 4

Ви можете використовувати функцію Async для цих ситуацій. Коли ви користуєтесь DispatchGroup(), Іноді може виникнути глухий кут .

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!)
        }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.