【问题标题】:How do I sleep in Swift Playgrounds and get the latest async values?如何在 Swift Playgrounds 中睡觉并获取最新的异步值?
【发布时间】:2020-07-10 20:27:18
【问题描述】:

我想在 Swift Playgrounds 中测试/显示与延迟后改变值的函数相关的行为。为简单起见,假设它改变了一个字符串。我知道我可以通过DispatchQueue.main.asyncAfter 延迟更新值的执行,并且我可以使用usleepsleep 休眠当前线程。

但是,由于 Playground 似乎在同步线程中运行,所以我无法看到睡眠后的变化。

这是我想做的一个例子:

var string = "original"

let delayS: TimeInterval = 0.100
let delayUS: useconds_t = useconds_t(delayS * 1_000_000)

func delayedUpdate(_ value: String) {
  DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delayS) {
    string = value
  }
}

delayedUpdate("test2")
assert(string == "original")
usleep(delayUS)
print(string) // ❌ Prints "original"
assert(string == "test2") // ❌ Assertion failure. string is "original" here

delayedUpdate("test3")
assert(string == "test2") // ❌ Assertion failure. string is "original" here
usleep(delayUS)
print(string) // ❌ Prints "original"
assert(string == "test3") // ❌ Assertion failure. string is "original" here

delayedUpdate("test4")
assert(string == "test3") // ❌ Assertion failure. string is "original" here
usleep(delayUS)
print(string) // ❌ Prints "original"
assert(string == "test4") // ❌ Assertion failure. string is "original" here

请注意所有失败的断言,因为顶层的任何内容都看不到对 string 的更改。这似乎是一个同步与异步线程的问题。

我知道我可以通过将usleep 替换为更多asyncAfter 来修复它:

delayedUpdate("test2")
assert(string == "original")
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delayS) {
  print(string)
  assert(string == "test2")

  delayedUpdate("test3")
  assert(string == "test2")
  DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delayS) {
    print(string)
    assert(string == "test3")

    delayedUpdate("test4")
    assert(string == "test3")
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delayS) {
      print(string)
      assert(string == "test4")
    }
  }
}

但是,每次应用程序延迟时,这都会导致缩进代码的金字塔。这 3 个关卡还不错,但如果我有一个大操场,这将变得非常难以遵循。

有没有办法使用更接近第一种线性编程风格的东西,尊重延迟后更新的更新?


另一个可能的解决方案是将每个对 string 的引用包装在 asyncAfter 中:

delayedUpdate("test2")
assert(string == "original")
usleep(delayUS)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.001) { print(string) }
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.001) { assert(string == "test2") }

delayedUpdate("test3")
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.001) { assert(string == "test2") }
usleep(delayUS)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.001) { print(string) }
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.001) { assert(string == "test3") }

delayedUpdate("test4")
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.001) { assert(string == "test3") }
usleep(delayUS)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.001) { print(string) }
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.001) { assert(string == "test4") }

但是,这也不是首选,因为它也非常混乱,并且例如,如果一次执行依赖于 string 的先前值来执行其功能,则可能容易出错。它还需要0.001 或类似的更正,以确保不存在竞争条件。


如何在 Swift Playground 中使用线性编程风格(例如使用 sleep),但在睡眠期间更新的值会被 sleep 之后的行正确反映?

【问题讨论】:

    标签: swift multithreading swift-playground


    【解决方案1】:

    您正在创建竞争条件。忘记游乐场;只需考虑以下代码:

        print("start")
        DispatchQueue.main.asyncAfter(deadline:.now() + 1) {
            print("delayed")
        }
        sleep(2)
        print("done")
    

    我们延迟 1 秒并打印“延迟”,我们休眠 2 秒并打印“完成”。你认为哪个会首先出现,“延迟”还是“完成”?如果你认为“延迟”会先出现,那你就不明白sleep 做了什么。它阻塞了主线程。延迟不能重新进入主线程,直到阻塞消失。

    【讨论】:

    • 啊,太好了。感谢您提供的示例,它也让事情变得一目了然。我想我试图在操场的顶层运行异步代码,Swift 不支持。
    • 当然你可以“在操场的顶层运行异步代码”。我的意思是,这不是你问的。你展示了一个自阻塞的竞争条件。如果你没有那个,你当然可以在操场上使用dispatchAfter
    【解决方案2】:

    matt 的简化示例使这段代码的真正问题非常清楚。并不是主队列没有看到值的更新,而是代码的执行顺序与我预期的不同。

    你可以把执行的顺序想象成:

    1. Swift Playground 顶层的所有行(主队列)
    2. 主队列中安排的任何其他内容

    这就解释了为什么代码按这个顺序执行:

    print("start") // 1
    DispatchQueue.main.asyncAfter(deadline:.now() + 1) {
        print("delayed") // 3
    }
    sleep(2)
    print("done") // 2
    // All async code on the main thread will execute after this line
    

    注意它是如何以 132 顺序而不是所需的 123 顺序执行的。原因是 Playground 中的顶级代码在主线程上运行,并且 .main 被提供给 DispatchQueue

    如果您尝试使用semaphore,您也可以清楚地看到问题:

    print("start") // 1
    let semaphore = DispatchSemaphore(value: 0)
    DispatchQueue.main.asyncAfter(deadline:.now() + 1) {
      print("delayed") // never executed
      semaphore.signal()
    }
    semaphore.wait() // This causes a deadlock
    print("done") // never executed
    

    semaphore.wait() 行导致死锁,因为semaphore.signal() 只能在打印done 之后调用,但由于wait,它无法前进到该行。

    让代码按所需顺序运行的一种方法是将异步代码移动到不同的线程(例如global()):

    print("start") // 1
    DispatchQueue.global().asyncAfter(deadline:.now() + 1) {
        print("delayed") // 2
    }
    sleep(2)
    print("done") // 3
    

    只需更改为使用global 队列而不是main 队列(并添加更多时间缓冲区)即可使原始代码按预期运行:

    var string = "original"
    
    let delayS: TimeInterval = 0.100
    let sleepDelayUS: useconds_t = useconds_t(delayS * 1_000_000)
    let sleepPaddingUS = useconds_t(100_000)
    
    func delayedUpdate(_ value: String) {
      DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + delayS) {
        string = value
      }
    }
    
    delayedUpdate("test2")
    assert(string == "original")
    usleep(sleepDelayUS + sleepPaddingUS)
    print(string) // ❌ Prints "original"
    assert(string == "test2") // ❌ Assertion failure. string is "original" here
    
    delayedUpdate("test3")
    assert(string == "test2") // ❌ Assertion failure. string is "original" here
    usleep(sleepDelayUS + sleepPaddingUS)
    print(string) // ❌ Prints "original"
    assert(string == "test3") // ❌ Assertion failure. string is "original" here
    
    delayedUpdate("test4")
    assert(string == "test3") // ❌ Assertion failure. string is "original" here
    usleep(sleepDelayUS + sleepPaddingUS)
    print(string) // ❌ Prints "original"
    assert(string == "test4") // ❌ Assertion failure. string is "original" here
    

    在 Playground 中测试具有延迟的代码的另一个选项是使用 XCTWaitersince XCTest works in Playgrounds。如果您想要一种更好的测试方式,您可以使用用于测试的自定义调度队列,而不是等待时间过去。 This episode on PointFree 解释了一种方法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-13
      • 1970-01-01
      • 1970-01-01
      • 2020-07-18
      • 1970-01-01
      • 2021-10-11
      • 1970-01-01
      • 2012-11-03
      相关资源
      最近更新 更多