swift - 主线程上的 BlockOperation 的 start()

标签 swift

为什么在主线程上调用具有超过 1 个 block 的 BlockOperation 的 start() 而不是在主线程上调用其 block ? 我的第一个测试总是通过,但第二个测试并非每次都通过 - 有时 block 不在主线程上执行

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

最佳答案

正如安德烈亚斯指出的那样,the documentation warns us :

Blocks added to a block operation are dispatched with default priority to an appropriate work queue. The blocks themselves should not make any assumptions about the configuration of their execution environment.

我们启动操作的线程以及队列的maxConcurrentOperationCount行为是在操作级别管理的,而不是在单个执行 block 中管理的一个手术。向现有操作添加 block 与向队列添加新操作不同。操作队列管理操作之间的关系,而不是操作内的 block 之间的关系。

通过让这些 block 做一些需要一点时间的事情可以暴露这个问题。考虑一个等待一秒钟的任务(您通常永远不会 sleep ,但我们这样做只是为了模拟一个缓慢的任务并表现出相关的行为)。我还添加了必要的“兴趣点”代码,以便我们可以在 Instruments 中观看此内容,这使得更容易可视化正在发生的情况:

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)

现在,我将其添加到串行操作队列中(因为您永远不会向主队列添加阻塞操作...我们需要保持该队列空闲且响应灵敏),但是如果您可以在 OperationQueue.main 上手动启动此操作。因此,底线是,虽然 start 将“立即在当前线程中”运行操作,但使用 addExecutionBlock 添加的任何 block 都只会在“适当的线程”上并行运行。工作队列”,不一定是当前线程。

如果我们在 Instruments 中观察这一点,我们可以看到,addExecutionBlock 不仅不一定遵守启动操作的线程,而且也不遵守队列的串行性质,或者, block 并行运行:

Parallel

显然,如果您将这些 block 添加为单独的操作,那么一切都很好:

for i in 1 ... 3 {
    let operation = BlockOperation {
        self.someTask("main block\(i)")
    }
    queue.addOperation(operation)
}

产量:

enter image description here

关于swift - 主线程上的 BlockOperation 的 start(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60302035/

相关文章:

ios - TableView 手势识别器影响滑动删除行

ios - IB Designables 在使用自定义类时发出奇怪的警告

ios - 从 Swift 函数中的异步调用返回数据

ios - 从 ViewController 访问编程 View ?

json - 如何使用 SwiftyJSON 从 JSON 对象获取 JSON key ?