使用 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)
  

网络调用的完成处理程序可以更新Sessionstate 属性。例如,用户选择 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 返回时遇到问题