![](/img/trans.png)
[英]Weird intermittent crash on synchronous start of an NSOperation on main thread
[英]start() for BlockOperation on the main thread
為什么在主線程上調用超過 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)
}
產量:
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.