GCD DispatchGroup异步任务
GCD DispatchGroup异步任务

GCD DispatchGroup异步任务

DispatchGroup 是 GCD(Grand Central Dispatch) 提供的一个机制,用于跟踪一组异步任务的完成情况。

可以在多个任务开始时调用 .enter(),每个任务完成后调用 .leave(),最后用 .notify() 或 .wait() 来统一处理这些任务完成后的逻辑。

用途场景

等待多个异步任务完成之后,统一更新 UI;

下载多个文件,全部完成后打包;

并行执行多个任务,全部结束后提交结果。

组成结构

1、enter():表示将要开始一个任务,告诉 DispatchGroup:要开始工作了。

2、.leave():表示完成了一个任务,告诉 DispatchGroup:这项任务做完了。必须和 .enter() 配对使用,多一次 leave() 就崩溃,少一次也会卡住不触发 notify()。

3、.notify(queue:):当所有 enter/leave 都配对完毕,任务组结束时被调用,可以在这里更新 UI 等。

4、.wait():同步阻塞当前线程,直到所有任务完成。不建议在主线程使用(容易卡 UI)。

使用示例

1、加载三张图片后,再更新UI:

let group = DispatchGroup()

var images: [NSImage] = []

for i in 1...3 {
    group.enter()
    loadImageAsync(index: i) { image in
        if let img = image {
            images.append(img)
        }
        group.leave()
    }
}

// 所有图片加载完毕后,通知主线程更新 UI
group.notify(queue: .main) {
    print("全部加载完成,共 \(images.count) 张图")
}

在for-in循环中,调用了闭包。

如果不使用DispatchGroup,那么可能进入第一次循环后,闭包因为是异步的,所以就会输出“全部加载完成”的信息。

使用DispatchGroup后,会等待异步闭包完成后,再输出“全部加载完成”。

实际案例

在压缩图片的应用中,我使用onDrop方法将图片拖入应用中。

当我拖入图片后,发现应用没有显示任何图片。

onDrop代码为:

.onDrop(of: [.image], isTargeted: $isHovering) { providers in
    var imageURLs: [URL] = []
    for provider in providers {
        if provider.hasItemConformingToTypeIdentifier(UTType.image.identifier) {
            provider.loadFileRepresentation(forTypeIdentifier: UTType.image.identifier) { url, error in
                guard let fileURL = url, let imageURL = saveURLToTempFile(fileURL: fileURL) else { return }
                imageURLs.append(imageURL)  // 将图片添加到队列
            }
        }
    }
    savePictures(url: imageURLs)    // 调用存储图片的方法
}

这里的流程是,使用for-in方法遍历所有拖入图片,将所有图片的URL添加到imageURLs数组中,最后调用存储图片的方法。

但代码实际上并未按照我想要的流程实现。

我尝试添加输出语句,检查imageURLs数组:

print("当前保存的URL数量:\(imageURLs.count)")
savePictures(url: imageURLs)

print("imageURL:\(imageURL),添加到队列")

发现输出的内容为:

当前保存的URL数量:0
imageURL:file:///Users/fangjunyu/Library/Containers/com.fangjunyu.ImageSlim/Data/tmp/eso1907a_%E5%89%AF%E6%9C%AC2.tif,添加到队列

这表示还未进入for-in循环,就先调用的savePictures方法。

这实际上是Swift异步的经典陷阱:因为 loadFileRepresentation 是一个异步方法,它不会阻塞 for 循环的执行。

for provider in providers {
    provider.loadFileRepresentation(...) { url, error in
        // 这个是异步回调,会稍后执行
        imageURLs.append(imageURL)
    }
}
savePictures(url: imageURLs) // 这行在闭包还没执行时就已经调用了

这也就意味着异步方法loadFileRepresentation会立即返回,闭包稍后执行。

savePictures(url: imageURLs) 这句会立即在 for 循环结束后调用;

这时imageURLs 还是空的,因为闭包尚未执行,所以应用不会添加任何图片。

解决方案就是使用DispatchGroup,当所有图片都处理完后,统一压缩/添加。

.onDrop(of: [.image], isTargeted: $isHovering) { providers in
    var imageURLs: [URL] = []
    let group = DispatchGroup() // 创建 DispatchGroup
    for provider in providers {
        if provider.hasItemConformingToTypeIdentifier(UTType.image.identifier) {
            group.enter()   // 开始任务
            provider.loadFileRepresentation(forTypeIdentifier: UTType.image.identifier) { url, error in
                defer { group.leave() } // 结束任务
                guard let fileURL = url, let imageURL = saveURLToTempFile(fileURL: fileURL) else { return }
                imageURLs.append(imageURL)  // 将图片添加到队列
            }
        }
    }

    group.notify(queue: .main) {    // 所有图片添加完成后,再调用压缩图片方法
        savePictures(url: imageURLs)
    }
}

通过group.enter()和group.leave(),在使用异步方法时,会添加异步任务。

当for-in的异步方法全部调用完成后,再调用group.notify(queue: .main)方法,统一压缩图片。

最终实现添加图片的真实场景。

总结

DispatchGroup适用于异步任务,在异步任务的场景下,如果不使用DispatchGroup,就会导致应用处理异步任务时,应用会继续调用其他的代码,这样就会破坏想要实现的任务顺序。

通过DispatchGroup可以实现在全部异步任务完成后,调用相关的完成代码。

enter()和leave()必须成对使用,否则会导致崩溃或卡住。

不要再主线程使用 wait()方法,否则会卡住UI。

相关文章

1、Swift管理多线程任务框架GCD:https://fangjunyu.com/2024/11/02/swift%e7%ae%a1%e7%90%86%e5%a4%9a%e7%ba%bf%e7%a8%8b%e4%bb%bb%e5%8a%a1%e6%a1%86%e6%9e%b6gcd/

2、Swift中的线程模型:https://fangjunyu.com/2024/12/25/swift%e4%b8%ad%e7%9a%84%e7%ba%bf%e7%a8%8b%e6%a8%a1%e5%9e%8b/

   

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开发者。

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注