start() для BlockOperation в основном потоке
Почему вызов start() для BlockOperation с более чем 1 блоком в основном потоке не вызывает его блок в основном потоке? Мой первый тест всегда проходит, но второй не каждый раз - иногда блоки выполняются не в основном потоке
func test_callStartOnMainThread_executeOneBlockOnMainThread() {
let blockOper = BlockOperation {
XCTAssertTrue(Thread.isMainThread, "Expect first block was executed on Main Thread")
}
blockOper.start()
}
func test_callStartOnMainThread_executeTwoBlockOnMainThread() {
let blockOper = BlockOperation {
XCTAssertTrue(Thread.isMainThread, "Expect first block was executed on Main Thread")
}
blockOper.addExecutionBlock {
XCTAssertTrue(Thread.isMainThread, "Expect second block was executed on Main Thread")
}
blockOper.start()
}
Даже следующий код не работает
func test_callStartOnMainThread_executeTwoBlockOnMainThread() {
let asyncExpectation = expectation(description: "Async block executed")
asyncExpectation.expectedFulfillmentCount = 2
let blockOper = BlockOperation {
XCTAssertTrue(Thread.isMainThread, "Expect first block was executed on Main Thread")
asyncExpectation.fulfill()
}
blockOper.addExecutionBlock {
XCTAssertTrue(Thread.isMainThread, "Expect second block was executed on Main Thread")
asyncExpectation.fulfill()
}
OperationQueue.main.addOperation(blockOper)
wait(for: [asyncExpectation], timeout: 2.0)
}
1 ответ
Как отметил Андреас, документация предупреждает нас:
Блоки, добавленные в операцию блока, отправляются с приоритетом по умолчанию в соответствующую рабочую очередь. Сами блоки не должны делать никаких предположений о конфигурации среды их выполнения.
Нить, на которой мы start
операция, а также maxConcurrentOperationCount
поведение очереди управляется на уровне операции, а не отдельными исполнительными блоками внутри операции. Добавление блока к существующей операции - это не то же самое, что добавление новой операции в очередь. Очередь операций определяет отношения между операциями, а не между блоками внутри операции.
Проблема может быть раскрыта, если заставить эти блоки делать что-то, что требует немного времени. Рассмотрим задачу, которая ждет одну секунду (обычно вы никогда неsleep
, но мы делаем это просто для имитации медленной задачи и демонстрации рассматриваемого поведения). Я также добавил необходимый код "интересных мест", чтобы мы могли наблюдать за этим в инструментах, что упрощает визуализацию происходящего:
import os.log
let pointsOfInterest = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: .pointsOfInterest)
func someTask(_ message: String) {
let id = OSSignpostID(log: pointsOfInterest)
os_signpost(.begin, log: pointsOfInterest, name: "Block", signpostID: id, "Starting %{public}@", message)
Thread.sleep(forTimeInterval: 1)
os_signpost(.end, log: pointsOfInterest, name: "Block", signpostID: id, "Finishing %{public}@", message)
}
Затем используйте addExecutionBlock
:
let queue = OperationQueue() // you get same behavior if you replace these two lines with `let queue = OperationQueue.main`
queue.maxConcurrentOperationCount = 1
let operation = BlockOperation {
self.someTask("main block")
}
operation.addExecutionBlock {
self.someTask("add block 1")
}
operation.addExecutionBlock {
self.someTask("add block 2")
}
queue.addOperation(operation)
Теперь я добавляю это в очередь последовательных операций (потому что вы никогда не добавили бы операцию блокировки в основную очередь... нам нужно, чтобы эта очередь оставалась свободной и отзывчивой), но вы увидите то же поведение, если вручную start
это на OperationQueue.main
. Итак, итоги, покаstart
запустит операцию "немедленно в текущем потоке", любые блоки, которые вы добавляете с addExecutionBlock
просто будет запускаться параллельно в "соответствующей рабочей очереди", не обязательно в текущем потоке.
Если мы посмотрим на это в инструментах, мы увидим, что не только addExecutionBlock
не обязательно учитывать поток, в котором была запущена операция, но он также не учитывает последовательный характер очереди с блоками, работающими параллельно:
Очевидно, если вы добавите эти блоки как отдельные операции, то все будет хорошо:
for i in 1 ... 3 {
let operation = BlockOperation {
self.someTask("main block\(i)")
}
queue.addOperation(operation)
}
Урожайность: