Синхронизация Swift Array с DispatchQueue - PullRequest
0 голосов
/ 24 марта 2020

Если uploadFailed(for id: String), uploadSuccess() и updateOnStart(_ id: String) вызываются из одного потока (основной поток потока 1), я понимаю, что нам не понадобится синхронизированная очередь. Что делать, если функции вызываются из разных потоков для каждой загрузки. Где я могу обеспечить синхронизацию? Будет ли это и загрузка, и состояние, или только состояние?

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

1 Ответ

1 голос
/ 24 марта 2020

Использование синхронизации для обеспечения поточно-безопасного взаимодействия с FlowState совершенно допустимо.

Несколько замечаний:

  1. Вы представили две альтернативы:

    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)
    }
    
  2. Если нет, я бы предложил включить дезинфицирующее средство для нитей (TSAN) во время ваша разработка и тестирование. См. Включение средства очистки потока . Это может помочь вам разобраться в подобных проблемах.

  3. Вы не показываете никаких дополнительных чтений state, но если они есть, убедитесь, что вы также синхронизируете свои чтения. Все взаимодействия с state должны быть синхронизированы.

  4. Небольшая проблема: если вы собираетесь синхронизировать свой доступ к state, убедитесь, что вы установили private, чтобы ни один внешний код не имеет к нему доступа (иначе вы потенциально препятствуете попытке обеспечить поточно-ориентированное взаимодействие). Вам нужно обернуть все операции чтения и записи в вашем механизме синхронизации.

    Возможно, вам также следует сделать очередь syn c частной, так как нет необходимости также это подвергать.

...