簡體   English   中英

start() 用於主線程上的 BlockOperation

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM