swift - 主线程上的 BlockOperation 的 start()
问题描述
为什么在主线程上调用具有超过 1 个块的 BlockOperation 的 start() 而不在主线程上调用其块?我的第一个测试总是通过但不是每次都通过 - 有时块不在主线程上执行
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)
}
解决方案
正如 Andreas 指出的那样,文档警告我们:
添加到块操作的块以默认优先级分派到适当的工作队列。块本身不应对其执行环境的配置做出任何假设。
我们操作的线程start
以及 maxConcurrentOperationCount
队列的行为在操作级别进行管理,而不是在操作中的各个执行块中进行管理。向现有操作添加块与向队列添加新操作不同。操作队列管理操作之间的关系,而不是操作中的块之间的关系。
通过让这些块做一些需要一点时间的事情,可以解决这个问题。考虑一个等待一秒钟的任务(你通常不会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)
现在,我将其添加到串行操作队列中(因为您永远不会向主队列添加阻塞操作......我们需要保持该队列自由和响应),但如果您手动执行start
此操作,您会看到相同的行为上OperationQueue.main
。因此,最重要的是,虽然start
将“立即在当前线程中”运行操作,但您添加的任何块addExecutionBlock
都只会在“适当的工作队列”上并行运行,而不需要当前线程。
如果我们在 Instruments 中观察这一点,我们可以看到,它addExecutionBlock
不仅不一定尊重启动操作的线程,而且也不一定尊重队列的串行性质,因为块是并行运行的:
显然,如果您将这些块添加为单独的操作,那么一切都很好:
for i in 1 ... 3 {
let operation = BlockOperation {
self.someTask("main block\(i)")
}
queue.addOperation(operation)
}
产量:
推荐阅读
- tensorflow - 使用 tensorflow_model_server 和 ssl 配置
- java - 解析图像时出现错误:“org.apache.commons.imaging.ImageReadException:Jpeg 包含多个 Photoshop App13 段”
- google-maps - Flutter 谷歌地图,标记拖动
- c++ - c++ 多线程原子加载/存储
- php - 我的重定向是在 Apache2 服务器上上传网站后删除斜杠 - admin.site.comlogin 而不是 admin.site.com/login
- c# - 是否可以使用 linq 创建一个按给定位置排序的 csv 字符串?
- ios - 什么是开发吊舱?
- twilio - Twilio 在“总结”中的任务问题
- excel - 从另一个工作簿打开范围过滤器删除重命名选项卡另存为另一个工作簿 x20
- r - 对数刻度 y 轴 ggplot