【问题标题】:start() for BlockOperation on the main thread主线程上的 BlockOperation 的 start()
【发布时间】:2020-02-19 13:58:37
【问题描述】:

为什么在主线程上调用具有超过 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)
}

【问题讨论】:

  • 为什么你认为BlockOperation 将始终且仅在主线程上执行?检查文档:添加到块操作的块以默认优先级分派到适当的工作队列。块本身不应对其执行环境的配置做出任何假设。
  • 但是如果我希望我的任务只在主线程上执行呢? (我知道这可能会很奇怪)如果我调用 OperationQueue.main.addOperation(blockOper) - 我没有得到期望。块是否在主线程上执行 - 这取决于我不知道的事情
  • 如果你使用OperationQueue.main.addOperation(blockOper)而不是blockOper.start(),这确实应该在主线程中执行。但这不是您的代码中显示的内容,还是我有什么问题?
  • @AndreasOetjen - 不,即使那样,addExecutionBlock 模式仍然可以体现 OP 描述的行为。正如 Andreas 所引用的,在使用 addExecutionBlock 时,不要对执行环境做出任何假设。
  • @Rob 嗯,文档中似乎有点矛盾,我猜:据我了解,不要假设有关执行环境的任何内容仅与BlockOperation 本身,因为它不知道它将被排入哪个OperationQueue。使用OperationQueue.main 时,操作应始终在主线程中执行。至少这是我对 在应用程序的主线程上一次执行一个操作 的理解(但是 BlockOperation 文档声明 Blocks added ... 以默认优先级分派到适当的工作队列。 )

标签: swift blockoperation


【解决方案1】:

正如 Andreas 指出的那样,the documentation warns us

添加到块操作的块以默认优先级分派到适当的工作队列。块本身不应对其执行环境的配置做出任何假设。

我们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)

现在,我将其添加到串行操作队列(因为您永远不会将阻塞操作添加到主队列......我们需要保持该队列空闲和响应),但如果您看到相同的行为你在OperationQueue.main 上手动start 这个。因此,最重要的是,虽然start 将“立即在当前线程中”运行操作,但您使用addExecutionBlock 添加的任何块都只会在“适当的工作队列”上并行运行,而不需要当前线程。

如果我们在 Instruments 中观察这一点,我们可以看到 addExecutionBlock 不仅不一定尊重开始操作的线程,而且它也不尊重队列的串行性质,与块并行运行:

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

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

产量:

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-19
    相关资源
    最近更新 更多