使用 DispatchQueue 实现 Swift 数组同步
Posted
技术标签:
【中文标题】使用 DispatchQueue 实现 Swift 数组同步【英文标题】:Swift Array Synchronization with DispatchQueue 【发布时间】:2020-03-24 04:20:59 【问题描述】:如果从同一个线程(线程 1 主线程)调用 uploadFailed(for id: String)
、uploadSuccess()
和 updateOnStart(_ id: String)
,我知道我们不需要同步队列。如果每次上传时从不同的线程调用函数怎么办。我在哪里确保同步?是上传和状态还是只是状态?
enum FlowState
case started(uploads: [String])
case submitted
case failed
class Session
var state: FlowState
let syncQueue: DispatchQueue = .init(label: "Image Upload Sync Queue",
qos: .userInitiated,
attributes: [],
autoreleaseFrequency: .workItem)
init(state: FlowState)
self.state = state
mutating func updateOnStart(_ id: String)
guard case .started(var uploads) = state else
return
uploads.append(id)
state = .started(uploads)
mutating func uploadFailed(for id: String)
guard case .started(var uploads) = state else
return
uploads.removeAll $0 == id
if uploads.isEmpty
state = .failed
else
state = .started(uploads)
mutating func uploadSuccess()
state = .submitted
我们是否同步uploads
数组操作和如下状态?
syncQueue.sync
uploads.append(id)
state = .started(uploads)
syncQueue.sync
uploads.removeAll $0 == id
if uploads.isEmpty
state = .failed
else
state = .started(uploads)
或
syncQueue.sync
state = .started(uploads)
syncQueue.sync
if uploads.isEmpty
state = .failed
else
state = .started(uploads)
网络调用的完成处理程序可以更新Session
的state
属性。例如,用户选择 10 张图像并上传。完成后,它可能是失败或成功。对于我们上传的每张图片,我们都会缓存资源id
,如果上传失败,我们会将其删除。当所有图片上传失败时,我们更新状态.failed
。我们只关心上传一张图片。当单张图片上传时,我们将状态更新为.submitted
【问题讨论】:
您所说的“是上传和状态还是只是状态?”是什么意思? 您能提供您具体用例的背景吗?你想做什么? @JoshWolff 我用更多信息更新了问题 “如果每次上传时从不同的线程调用函数怎么办” 也许这似乎是一种肤浅的反应,但我的第一个想法是确保不会发生这种情况是你的工作。 .? 回调尚未创建,其他人将处理异步同时上传的上传。这只是一个后备 【参考方案1】:使用同步来确保与FlowState
的线程安全交互是完全有效的。
一些观察:
您提出了两种选择:
syncQueue.sync
uploads.append(id)
self.state = .started(uploads)
或者
syncQueue.sync
state = .started(uploads)
这些都不对。如果线程 1 和线程 2 同时调用这个例程怎么办?考虑:
线程 1 检索到uploads
线程 2 检索到相同的uploads
;
然后线程 1 进入它的 sync
块并将一条记录附加到它的本地副本,保存它,然后离开它的 sync
闭包;
然后线程 2 进入它的 sync
块并将不同的记录附加到它自己的本地副本(没有由线程 1 添加的记录),保存它,然后离开它的 sync
闭包。
在这种情况下,您将丢失附加的线程 1。
因此,您需要采用第三种更广泛的同步机制,将检索、附加和存储uploads
的整个过程封装在一个同步机制中:
syncQueue.sync
guard case .started(var uploads) = self.state else
return
uploads.append(id)
state = .started(uploads)
如果您还没有,我建议您在开发和测试期间打开thread sanitizer (TSAN)。它可能会帮助您发现这些问题。
您没有显示任何对 state
的额外读取,但如果有任何其他读取,请确保您也同步读取。与state
的所有交互都必须同步。
一个微妙的问题:如果您要同步对 state
的访问,请确保将其设置为 private
,以便没有外部代码可以访问它(否则您可能会阻碍您确保线程安全的交互)。您需要将所有读取和写入都包含在同步机制中。
您可能也应该将同步队列设为私有,因为也不需要公开它。
【讨论】:
谢谢罗伯。那真的很有帮助。public private(set) var state: FlowState
状态只能公开阅读
无意冒犯,但是当您可能正在写作时,您不能让其他线程阅读此内容。您不想公开任何未同步的访问,即使只是读取。以上是关于使用 DispatchQueue 实现 Swift 数组同步的主要内容,如果未能解决你的问题,请参考以下文章
Swift 5:我无法让我的 UITableView 及时更新(同时使用 `DispatchQueue.global().sync` 和 `DispatchQueue.main.async`
dispatchqueue 在 Swift 4 中异步使用以加速
Swift:使用 DispatchQueue.global (qos: .userInitiated) .asyncAfter 重复返回的结果
为啥在 swift 的 mac 命令行工具中使用 DispatchQueue.main.async 时需要运行循环?
iOS (Swift):使用 DispatchQueue.main.asyncAfter 制作充满活力的按钮 Flash
在使用 Swift 的 iOS 应用程序中使用 DispatchQueue 等待数据从 Cloud Firestore 返回时遇到问题