Ожидание завершения задачи

100

Как я могу заставить свой код ждать завершения задачи в 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.

Бартош Возняк
источник

Ответы:

234

Для этого используйте 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подход (если для вас нет проблем с блокировкой, т.е. если вы не в основном потоке), либо предоставьте обработчик завершения, либо используйте подход уведомления в вызывающем классе.
shallowThought
Почему вы звоните group.enterза пределы асинхронного блока? Разве каждый блок не должен входить в группу и выходить из нее?
Билл
4
@Bill waitждет , пока enterи leaveвызовы сбалансированы. Если вы положили enterв закрытии, waitне ждать бы потому , что enterеще не было , и , таким образом , называется число enterи leaveвызовы будут сбалансированы (# введите == 0, # leav == 0).
shallowThought
1
@rustyMagnet для тестов, скорее всего, это не выход. XCTTestExpectationВместо этого используйте s. См. Этот образец кода
shallowThought
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
    }

И последнее: если вы хотите использовать завершениеHandler, когда ваша задача завершается с помощью 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
Я перепробовал их все, и ни один из них не работал, пока пытался обрабатывать вызовы
3

Swift 5 версия решения

func myCriticalFunction () {var value1: String? var value2: String?

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

}

Сайкришна Рао
источник
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
@shallowThought Так верно.
Prateekro
Я хочу выполнить функцию в другом классе, но хочу дождаться завершения этой функции, а затем продолжить в текущем классе, как я могу с этим справиться?
Саид Рахматолахи
1
Хотя приведенный выше пример блокирует основной поток, как показали предыдущие ответы, перенос внутри DispatchQueue.global().async{}не блокирует основную очередь.
JonnyB
1

Swift 4

В этих ситуациях вы можете использовать асинхронную функцию. Когда вы используете 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!)
        }
    }
}
Али Ихсан УРАЛ
источник