Отправка работы в несколько очередей и ожидание синхронно - PullRequest
0 голосов
/ 26 октября 2019

Я хочу пройти тест ниже. В моем реальном коде AsyncClass отправляет работу в несколько очередей, используя класс DispatchGroup. В Swift мы работаем с @escaping обработчиками завершения для асинхронной работы. Я ищу способ заставить вызывающий поток дождаться окончания работы dispatchGroup. Таким образом, закрытие может исчезнуть.

Короче говоря, у меня есть вызывающий поток (основной), который вызывает функцию, которая отправляет работу в несколько очередей. Я хочу, чтобы вызывающий поток был заблокирован, когда эта работа продолжается, и был разблокирован, когда работа завершена. Это приводит к тому, что @escaping completeHandler может исчезнуть, и я могу нормально вызывать функцию без замыкания (и когда я перехожу на следующую строку после вызова метода, работа полностью выполняется, конечно)

Я используюэтот код только для тестов, я знаю, что никогда не должен блокировать основной поток в производстве.

Usecase: у меня есть тесты, которые вызывают дорогой метод. Этот метод выполняет работу как «AsyncClass» в этом примере. Внутри AsyncClass я рассылаю работу некоторым потокам, чтобы ускорить процесс. Теперь, во время тестирования, я могу все время создавать ожидания и вызывать этот метод с замыканием, но это для меня многословно. Я хочу превратить асинхронный метод в метод синхронизации, для простоты использования.

import XCTest

class DispatchGroupTestTests: XCTestCase {

    func testIets() {
        let clazz = AsyncClass()

        var isCalled = false

        clazz.doSomething {
            isCalled = true
        }

        XCTAssert(isCalled)
    }

}

class AsyncClass {
    func doSomething(completionHandler: @escaping () -> ()) {
        let dispatchGroup = DispatchGroup()

        for _ in 0...5 {
            dispatchGroup.enter()

            DispatchQueue.global().async {
                let _ = (0...10000).map { $0 * 1000 }

                dispatchGroup.leave()
            }
        }

        dispatchGroup.wait() // Doesn't work

        dispatchGroup.notify(queue: .main) {
            completionHandler()
        }
    }
}

1 Ответ

0 голосов
/ 27 октября 2019

Мне удалось сделать это спин-блокировкой (просто перевести нить в цикл while):

import XCTest

class DispatchGroupTestTests: XCTestCase {

    func testIets() {
        let clazz = AsyncClass()

        var isCalled = false

        clazz.doSomething {
            isCalled = true
        }

        XCTAssert(isCalled)
    }

}

class AsyncClass {
    func doSomething(completionHandler: () -> ()) {
        var isDone = false
        let dispatchGroup = DispatchGroup()

        for _ in 0...5 {
            dispatchGroup.enter()

            DispatchQueue.global().async {
                let _ = (0...1000000).map { $0 * 10000 }

                dispatchGroup.leave()
            }
        }

        dispatchGroup.notify(queue: .global()) {
            isDone = true
        }

        while !isDone {
            print("sleepy")
            Thread.sleep(forTimeInterval: 0.1)
        }

        completionHandler()
    }
}

Теперь замыкание больше не нужно, и ожидания можно снять (хотя яв приведенных примерах их нет, я могу опустить их в своем «реальном» коде тестирования):

import XCTest

class DispatchGroupTestTests: XCTestCase {

    var called = false

    func testIets() {
        let clazz = AsyncClass()

        clazz.doSomething(called: &called)

        XCTAssert(called)
    }

}

class AsyncClass {
    func doSomething(called: inout Bool) {
        var isDone = false
        let dispatchGroup = DispatchGroup()

        for _ in 0...5 {
            dispatchGroup.enter()

            DispatchQueue.global().async {
                let _ = (0...1000000).map { $0 * 10000 }

                dispatchGroup.leave()
            }
        }

        dispatchGroup.notify(queue: .global()) {
            isDone = true
        }

        while !isDone {
            print("sleepy")
            Thread.sleep(forTimeInterval: 0.1)
        }

        called = true
    }
}

Итак, я провел тест производительности с блоком меры и моими ожиданиями (не ожиданиями XCTestCase)выполнить: быстрее распределить работу на другие потоки и заблокировать вызывающий поток в спин-блокировке, сравнивая с отправкой всей работы вызывающему потоку (учитывая, что мы не хотим экранировать блоки и хотим, чтобы все синхронизировалось, только длялегкий вызов функций внутри тестовых методов). Я буквально просто заполнил случайные вычисления, и это был результат:

import XCTest

let work: [() -> ()] = Array.init(repeating: { let _ = (0...1000000).map {  $0 * 213123 / 12323 }}, count: 10)

class DispatchGroupTestTests: XCTestCase {

    func testSync() {
        measure {
            for workToDo in work {
                workToDo()
            }
        }
    }

    func testIets() {
        let clazz = AsyncClass()

        measure {
            clazz.doSomething()
        }
    }

}

class AsyncClass {
    func doSomething() {
        var isDone = false
        let dispatchGroup = DispatchGroup()

        for workToDo in work {
            dispatchGroup.enter()

            DispatchQueue.global().async {
                workToDo()

                dispatchGroup.leave()
            }
        }

        dispatchGroup.notify(queue: .global()) {
            isDone = true
        }

        while !isDone {
            Thread.sleep(forTimeInterval: 0.1)
        }
    }
}

enter image description here

...