es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

¿Cuál es la mejor solución para tareas en segundo plano utilizando Swift async, await, @MainActor?

Estoy estudiando async, await, @MainActor de Swift.

Quiero ejecutar un proceso largo y mostrar el progreso.

import SwiftUI

@MainActor
final class ViewModel: ObservableObject {
    @Published var count = 0

    func countUpAsync() async {
        print("countUpAsync() isMain=\(Thread.isMainThread)")
        for _ in 0..<5 {
            count += 1
            Thread.sleep(forTimeInterval: 0.5)
        }
    }

    func countUp() {
        print("countUp() isMain=\(Thread.isMainThread)")
        for _ in 0..<5 {
            self.count += 1
            Thread.sleep(forTimeInterval: 0.5)
        }
    }
}

struct ContentView: View {
    @StateObject private var viewModel = ViewModel()

    var body: some View {
        VStack {
            Text("Count=\(viewModel.count)")
                .font(.title)

            Button("Start Dispatch") {
                DispatchQueue.global().async {
                    viewModel.countUp()
                }
            }
            .padding()

            Button("Start Task") {
                Task {
                    await viewModel.countUpAsync()
                }
            }
            .padding()
        }
        .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Cuando presiono el botón “Start Dispatch”, el “Count” se actualiza pero recibo una advertencia:

No está permitido publicar cambios desde hilos en segundo plano; asegúrese de publicar valores desde el hilo principal (a través de operadores como receive(on:)) en las actualizaciones del modelo.

Pensé que la clase ViewModel es @MainActor, que la propiedad count se manipula en el hilo Main, pero no es así.
¿Debo utilizar DispatchQueue.main.async{} para actualizar count aunque tenga @MainActor?

Cuando presiono el botón “Start Task”, el botón se mantiene presionado hasta que countUpAsync() se complete y no se actualiza el contador en la pantalla.

¿Cuál es la mejor solución?

Tags:  , , ,

Answer

  1. Avatar for davy.ai

    Al trabajar con SwiftUI y concurrencia, es importante asegurarse de que cualquier actualización de la interfaz de usuario se realice en el hilo principal. Si bien la clase ViewModel está anotada con el atributo @MainActor, el problema de actualizar la propiedad count desde un hilo en segundo plano surge del hecho de que la instancia de ViewModel no se está ejecutando en el hilo principal.

    Para actualizar la propiedad count en el hilo principal, puedes utilizar DispatchQueue.main.async{} o la función await con el atributo @MainActor. Una solución para resolver la advertencia sería utilizar DispatchQueue.main.async{} para actualizar count en la función countUp() de la siguiente manera:

    func countUp() {
        DispatchQueue.main.async {
            print("countUp() isMain=\(Thread.isMainThread)")
            for _ in 0..<5 {
                self.count += 1
                Thread.sleep(forTimeInterval: 0.5)
            }
        }
    }
    

    De manera similar, para el botón “Iniciar tarea” que actualiza count de forma asíncrona, puedes utilizar la función await con el atributo @MainActor de la siguiente manera:

    Button("Iniciar tarea") {
        Task {
            await MainActor.run { // asegurar la ejecución en el hilo principal
                await viewModel.countUpAsync()
            }
        }
    }
    

    Al utilizar MainActor.run con la función await, se otorga permiso a la ejecución de countUpAsync() en un hilo en segundo plano para publicar actualizaciones de vuelta al hilo principal, lo que permite actualizar la interfaz de usuario en consecuencia.

    En general, es importante recordar que al tratar la concurrencia en SwiftUI, cualquier actualización de la interfaz de usuario debe realizarse en el hilo principal, incluso si la clase en sí misma ha sido marcada con el atributo @MainActor.

Comments are closed.