代码之家  ›  专栏  ›  技术社区  ›  salman siddiqui

在GCD中,串行队列通过异步操作Swift同步

  •  13
  • salman siddiqui  · 技术社区  · 6 年前

    我正在使用具有QoS后台的串行队列

    let serialQueue = DispatchQueue(label: "queue1", qos: DispatchQoS.background)
    

    同步分配一个作业,异步分配两个作业:

    func serialTask() {
        serialQueue.sync {
            for i in 0..<10 {
                print("🔷", i)
            }
        }
        serialQueue.async {
            for i in 20..<30 {
                print("⚪️", i)
            }
        }
    
        serialQueue.async {
            for i in 101..<120 {
                print("🔷", i)
            }
        }
    }
    

    所有3个作业都在相继执行同步,但最后两个作业是异步的。异步作业是否在串行队列中同步。

    3 回复  |  直到 6 年前
        1
  •  18
  •   Community Justin Hirsch    4 年前

    让我看看是否可以澄清 async vs。 sync

    在我的示例中,我将采用以下几个更改:

    1. 我将使用工具(兴趣点)来显示任务何时运行,而不是 print 声明。(见WWDC 2019 Getting Started With Instruments .)通过这种方式,我们可以以图形方式查看行为。

      在分派任务时,我将发布一个简单的兴趣点事件路标(),并将分派的任务包装在一个兴趣区域(一个水平条)中,以图形方式说明某个进程的持续时间。

    2. 我会换你的 for 循环为a Thread.sleep(forTimeInterval: 1) ,模拟一些耗时的过程。如果你能快速 对于 循环,事情将发生得如此之快,以至于无法辨别线程到底发生了什么。

    因此,请考虑:

    import os.signpost
    
    private let pointsOfInterest = OSLog(subsystem: "GCD Demo", category: .pointsOfInterest)
    
    func tasks(on queue: DispatchQueue) {
        pointsOfInterestRange(with: "tasks(on:)") {
            os_signpost(.event, log: pointsOfInterest, name: "1") // first Ⓢ
            queue.sync { self.oneSecondProcess(with: "1") }
    
            os_signpost(.event, log: pointsOfInterest, name: "2") // second Ⓢ
            queue.async { self.oneSecondProcess(with: "2") }
    
            os_signpost(.event, log: pointsOfInterest, name: "3") // third Ⓢ
            queue.async { self.oneSecondProcess(with: "3") }
        }
    }
    
    func oneSecondProcess(with staticString: StaticString) {
        pointsOfInterestRange(with: staticString) {
            Thread.sleep(forTimeInterval: 1)
        }
    }
    
    func pointsOfInterestRange(with staticString: StaticString, block: () -> Void) {
        let identifier = OSSignpostID(log: pointsOfInterest)
        os_signpost(.begin, log: pointsOfInterest, name: staticString, signpostID: identifier)
        block()
        os_signpost(.end, log: pointsOfInterest, name: staticString, signpostID: identifier)
    }
    

    这就像你的例子,但是 打印 声明,我们有路标声明,在Instruments Points of Interest tool中生成以下图形时间线:

    enter image description here

    因此,您可以看到:

    1. 这个 tasks(on:) 函数在底部发出 同步 调度,第一个路标。

    2. 它等待 同步 任务1,在继续之前完成,此时它会发出两个后续的调度,即第二个和第三个路标(这两个路标连续发生得很快,以至于它们在图中重叠)。

    3. 但是 任务(开:) 不等待这两个 异步 要完成的任务2和3。一旦完成这些任务 异步 任务,它会立即返回(因此 任务(开:) 范围在该点立即停止)。

    4. 因为后台队列是串行的,所以三个调度任务(1、2和3)依次运行。

    但是,如果将此更改为使用并发队列,请执行以下操作:

    let queue = DispatchQueue(label: "...", attributes: .concurrent)
    

    那么你可以看到 异步 现在,任务彼此同时运行:

    enter image description here

    这次, task(on:) 分派 同步 调用,等待它完成,然后 同步 通话结束,可以 seriesOfTasks 继续调度这两个 异步 调用(在本例中,不等待分派任务的调用完成)。

    如您所见 异步 同步 行为是不同的。具有 同步 调用线程将等待调度的任务完成,但 异步 ,它不会。


    从上面可以得出两个主要结论:

    1. 选择 同步 vs公司 异步 指示当前线程的行为(即是否应等待已调度的任务)。

      一般来说,我们会避免打电话 同步 在执行任何耗时的操作时(因为这最终会阻塞主线程)。

    2. 串行队列与并发队列的选择决定了您调度的工作的行为,即它是否可以与该队列上的其他任务同时运行,或者它们是否会一个接一个连续运行。

        2
  •  4
  •   Smartcat    6 年前

    同步和异步执行与底层队列无关。同步执行意味着调用线程必须等到块完成。因此,第二个块是 在之后排队 第一个块已完成。Asynchrounous表示调用方 不得 等待块完成。因此,第三个块是 排队 直接在之前的 serialQueue.async 语句,而第二个块仍在运行或等待执行。

    在功能结束时 serialTask() 保证执行第一个块。第二个和第三个块已排队,但不确定它们是否已执行、正在运行或甚至正在等待执行。由于您使用的是串行队列,因此可以确定第二个块是在第三个块之前执行的。

    您可以通过添加

    serialQueue.async {
        print "sleep"
        sleep(10);
        print "awake"
    }
    

    在两次调用之前 async ,您将遵守以下规定:

    1. 第一个块将立即执行,并且 sleep 将立即打印。
    2. 执行 serialTask() 所需时间大大少于10秒。
    3. awake 仅在10秒后输出(惊喜,惊喜)。
    4. 第二个和第三个块也在10秒后执行 serialTask() 结束。

    晚的 两个块的执行意味着 异步的 。如果要将第一个(同步)块移动到 serialTask() ,将发生以下情况:

    1. 睡觉 将立即打印。
    2. 执行 serialTask() 大约需要10秒钟。在最后的同步块执行完毕后完成。
    3. 唤醒 仅在10秒后输出(惊喜,惊喜)。
    4. 第二个和第三个块也在之后执行 唤醒 已打印出来。
        3
  •  0
  •   Smartcat    6 年前

    因此,同步任务立即运行,然后在完成时,向该队列添加两个异步任务,*然后方法返回*这两项任务将按顺序进行处理, 只要队列空闲

    因此,如果在调用此方法之前队列中有任何挂起的异步任务,则这些任务将在两个异步任务之前运行。