【问题标题】:How does the semaphore keep async loop in order?信号量如何保持异步循环有序?
【发布时间】:2018-12-01 16:46:47
【问题描述】:

我已设置此脚本以在后台循环遍历一堆数据,并且我已成功设置信号量以保持所有内容(将填充表格的数组)有序,但我无法完全理解如何或为什么信号量保持数组有序。输入dispatchGroup,循环停止并等待直到下载图像,一旦获得图像,dispatchSemaphore 设置为1,然后立即退出dispatchGroup,信号量设置回0 .信号量从 0 切换到 1 太快了,我不明白它是如何保持数组有序的。

let dispatchQueue = DispatchQueue(label: "someTask")
let dispatchGroup = DispatchGroup()
let dispatchSemaphore = DispatchSemaphore(value: 0)

dispatchQueue.async {

    for doc in snapshot.documents {

        // create data object for array

        dispatchGroup.enter()

        // get image with asynchronous completion handler
        Storage.storage().reference(forURL: imageId).getData(maxSize: 1048576, completion: { (data, error) in

            defer {
                dispatchSemaphore.signal()
                dispatchGroup.leave()
            }

            if let imageData = data,
                error == nil {
                // add image to data object
                // append to array
            }

        })

        dispatchSemaphore.wait()

    }

    // do some extra stuff in background after loop is done

}

dispatchGroup.notify(queue: dispatchQueue) {

    DispatchQueue.main.async {
        self.tableView.reloadData()
    }

}

【问题讨论】:

    标签: swift grand-central-dispatch semaphore


    【解决方案1】:

    解决方案在您的评论get image with asynchronous completion handler 中。如果没有信号量,所有图像下载将同时开始并竞相完成,因此下载速度最快的图像将首先添加到数组中。

    因此,在您开始下载后,您会立即等待您的信号量。这将阻塞,直到在 getData 方法的回调闭包中发出信号。只有这样循环才能继续到下一个文档并下载它。这样,您可以一个接一个地下载文件,并在下载运行时阻塞当前线程。

    这里不能选择使用串行队列,因为这只会导致下载以串行方式开始,但您不能影响它们完成的顺序。

    这是一个相当低效的方法。如果你同时给它多个请求,你的网络层可能会运行得更快(想想并行下载和 HTTP 管道)。另外,您正在“浪费”一个线程,该线程可以同时进行一些不同的工作。如果同时有更多工作要做,GCD 将产生另一个线程,这会浪费内存和其他资源。

    更好的模式是跳过信号量,让下载并行运行并将图像直接存储在数组中的正确索引处。这当然意味着您必须事先准备一个适当大小的数组,并且您必须为丢失或失败的图像考虑一个占位符。选项会很好地解决这个问题:

    var images: [UIImage?] = Array(repeating: nil, count: snapshot.documents.count)
    
    for (index, doc) in snapshot.documents.enumerated() {
    
        // create data object for array
    
        dispatchGroup.enter()
    
        // get image with asynchronous completion handler
        Storage.storage().reference(forURL: imageId).getData(maxSize: 1048576) { data, error in
    
            defer {
                dispatchGroup.leave()
            }
    
            if let imageData = data,
                error == nil {
                // add image to data object
                images[index] = image
            }
        }
    }
    

    【讨论】:

    • 惊人的答案。我绝对想尽可能多地释放线程,并且绝对会实现并行下载。我喜欢枚举循环并利用索引值的想法。
    • 在我的问题代码中,我在异步并发后台线程dispatchQueue.async { // loop } 中执行循环。如果我将其设为同步方法dispatchQueue.sync { // loop },这是否意味着该特定后台线程将被锁定,直到循环完成(串行执行)?而将其声明为异步仅意味着该特定线程可以在循环执行时并行执行其他操作?
    【解决方案2】:

    DispatchGroup 在这里并没有真正做任何事情。你有DispatchSemaphor授予的互斥,排序只是由snapshot.documents的迭代顺序提供的

    【讨论】:

    • 如果我从上面的代码中直接删除信号量,数组永远不会被填充。这是为什么呢?
    • @WeiLoPing 我的回答实际上是不正确的。我误读了代码。我现在正在修复它。
    猜你喜欢
    • 2018-01-23
    • 2019-07-02
    • 1970-01-01
    • 2020-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-17
    • 2023-01-20
    相关资源
    最近更新 更多