等待要在下一次分派时执行的块如何成功?

How does wait succeed for a block that is to be executed on the next dispatch?

import XCTest
@testable import TestWait

class TestWait: XCTestCase {

    func testX() {
        guard Thread.isMainThread else {
            fatalError()
        }
        let exp = expectation(description: "x")
        DispatchQueue.main.async {
            print("block execution")
            exp.fulfill()
        }
        print("before wait")
        wait(for: [exp], timeout: 2)
        print("after wait")
    }
}

输出:

before wait
block execution
after wait

我正在尝试使打印顺序合理化。这就是我的想法:

  1. 测试是 运行 在 main thead
  2. 它从主线程分派一个块,但是由于分派是从主线程发生的,所以块的执行必须等到当前块执行完
  3. "before wait" 打印
  4. 我们wait期望得到满足。此等待使当前线程(即主线程)休眠 2 秒。那么,即使我们还没有从主线程分派出去,世界上又是如何等待成功的呢?我的意思是 "after wait" 还没有打印出来!所以我们必须仍然在主线程上。因此 "block execution" 永远不可能发生。

我的解释有什么问题吗?我猜我一定是 wait 的实现方式

里面的wait函数很有可能利用了NSRunLoop。 运行 循环不会像 sleep 函数那样阻塞主线程。尽管执行了函数 testX 并没有继续。 运行 循环仍然接受在线程中安排的事件并分派它们执行。

更新:

这就是我设想的 运行 循环的工作方式。在伪代码中:

while (currentDate < dateToStopRunning && someConditionIsTrue()) 
{
   if (hasEventToDispatch()) //has scheduled block?
   {
        runTheEvent();// yes, we have a block, so we run it! 
   }
}

您为异步执行放置的块在 hasEventToDispatch() 方法中检查并执行。它满足了在 someConditionIsTrue() 中的 while 循环的下一次迭代中检查的期望,因此 while 循环退出。 testX 继续执行并打印 after wait

如有疑问,请查看源代码!

https://github.com/apple/swift-corelibs-xctest/blob/ab1677255f187ad6eba20f54fc4cf425ff7399d7/Sources/XCTest/Public/Asynchronous/XCTWaiter.swift#L358

整个等待代码并不简单,但实际等待归结为:

_ = runLoop.run(mode: .default, before: Date(timeIntervalSinceNow: timeIntervalToRun))

您不应该根据 线程 来考虑等待,而应该根据 队列 来考虑等待。通过 RunLoop.current.run() 你基本上告诉当前代码开始执行队列中的其他项目。

XCTestCasewait(for:timeout:) 不同于您可能熟悉的 GCD group/semaphore wait 函数。

当您调用 wait(for:timeout:), much like the GCD wait calls, it will not return until the timeout expires or the expectations are resolved. But, in the case of XCTestCase and unlike the GCD variations, inside wait(for:timeout:), it is looping, repeatedly calling run(mode:before:) 直到期望得到解决或超时。这意味着尽管 testXwait 满足之前不会继续,但对 run(mode:before:) 的调用将允许 运行 循环继续处理事件(包括派发给该事件的任何事件)队列,包括完成处理程序关闭)。因此没有死锁。

可能不用说,这是 XCTestCase 的一个特性,但不是您自己的代码中可以使用的模式。

无论如何,有关 运行 循环如何工作的更多信息,请参阅 Threading Programming Guide: Run Loops