Estou vindo de um background do PowerShell e, no momento, estou tentando entender o Swift Concurrency. Meu primeiro projeto relacionado à simultaneidade é um utilitário para transcrever arquivos de áudio em massa, e estou quebrando a cabeça com alguns conceitos básicos.
O que estou tentando fazer aqui é simplesmente obter uma lista dos arquivos de áudio, então fazer um loop por eles exibindo aquele que está sendo trabalhado na IU e usar um cronômetro simples para simular o trabalho de transcrição por enquanto. Eu encontrei [isto][1] que pensei que me permitiria usar um asyncAfter
cronômetro típico, mas não está funcionando.
Minha expectativa é que a interface do usuário mostre o nome do arquivo atual e, depois de um tempo, o console mostre o mesmo enquanto a interface do usuário mostra o próximo nome do arquivo, mas a interface do usuário está mostrando apenas o último nome do arquivo e todas as gravações no console sugerem que basicamente cada temporizador está sendo executado em paralelo.
Em um mundo perfeito, eu GOSTARIA de transcrever tudo em paralelo, mas esse é outro problema, parece que SFSpeechURLRecognitionRequest
não suporta isso. De fato, mesmo apenas em sequência me deu problemas, mas não tenho nem 100% de certeza de que ESTOU fazendo isso em sequência, então estou tentando ter alguma cabeça para forçar isso, daí o código com falha aqui.
struct ContentView: View {
@State private var fileName: String?
@State private var files: [URL] = []
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("\(fileName ?? "-")")
}
.padding()
.onAppear {
files = getFiles()
processFiles()
}
}
private func getFiles() -> [URL] {
do {
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let path = documentsURL.appendingPathComponent("Voice Memos").absoluteURL
let contents = try FileManager.default.contentsOfDirectory(at: path, includingPropertiesForKeys: nil, options: [])
let files = (contents.filter {$0.pathExtension == "m4a"}).sorted { url1, url2 in
url1.path > url2.path
}
return files
}
catch {
print(error.localizedDescription)
return []
}
}
private func processFiles() {
for file in files {
Task {
await processFile(file)
}
}
}
private func processFile(_ url: URL) async {
fileName = url.lastPathComponent
let seconds = Double.random(in: 10.0...20.0)
Task.synchronous {
DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
print("\(url.lastPathComponent) \(seconds)")
}
}
}
}
extension Task where Failure == Error {
/// Performs an async task in a sync context.
///
/// - Note: This function blocks the thread until the given operation is finished. The caller is responsible for managing multithreading.
static func synchronous(priority: TaskPriority? = nil, operation: @escaping @Sendable () async throws -> Success) {
let semaphore = DispatchSemaphore(value: 0)
Task(priority: priority) {
defer { semaphore.signal() }
return try await operation()
}
semaphore.wait()
}
}
EDIT: Eu também tentei
let duration: UInt64 = 2_000_000_000 /* 2 seconds */
var seconds: UInt64 { duration/1_000_000_000 }
Task.synchronous { try await Task.sleep(nanoseconds: duration) }
como a função de timer, e isso simplesmente trava. Quanto a quem votou negativamente instantaneamente, você pode pelo menos explicar POR QUE essa é uma pergunta tão ofensiva?
Os arquivos fazem o loop da sua tarefa create 4 rodar independentemente, eles não são executados em uma sequência. Então o fileName é definido para o nome do último arquivo imediatamente.
Também não deve misturar
Semaphore
comasync await
. Aqui pode haver um refator para seu código, também uso String para simplificar: