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.

Responder con mensaje mientras se generan los archivos

Tengo un servicio donde los usuarios pueden exportar datos a un archivo de Excel que genero para ellos en mi servidor backend. A veces, estas exportaciones pueden ser grandes. Por lo tanto, con el objetivo de tener en cuenta la experiencia de usuario, quiero confirmar que el servidor recibe la solicitud y responderá que los documentos se están generando y les serán enviados por correo electrónico una vez finalizados.

Este es un ejemplo básico de lo que tengo hasta ahora.

Código:

func generateDocx(n int, wg *sync.WaitGroup) {
    defer wg.Done()
    for i := 0; i < 10; i++ {
        amt := time.Duration(rand.Intn(250))
        time.Sleep(time.Millisecond * amt)
        fmt.Println("Exportando:", n, "Número del documento:", i)
    }
}

func ExportService() {
    wg := &sync.WaitGroup{}
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go generateDocx(i, wg)
    }
    wg.Wait()

    // enviar correo electrónico al usuario después de que se generen todos los documentos
    fmt.Println("Enviando correo electrónico de que los documentos están listos")
}

func main() {
    finish := make(chan bool)
    // var input string
    // fmt.Scanln(&input)

    router := mux.NewRouter()

    type Reply struct {
        Title   string
        Message string
    }

    router.HandleFunc("/export", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(http.StatusCreated)
        fmt.Println("comenzando generación")
        ExportService()
        json.NewEncoder(w).Encode(&Reply{
            Title:   "Generando",
            Message: "Se están generando sus archivos, recibirá un correo electrónico cuando los archivos estén listos.",
        })
        fmt.Println("respondido")
    })
    log.Fatal(http.ListenAndServe(":8080", router))
    <-finish
}

Cuando se alcanza el punto final:

comenzando generación
Exportando: 8 Número del documento: 0
Exportando: 6 Número del documento: 0
Exportando: 9 Número del documento: 0
Exportando: 6 Número del documento: 1
Exportando: 4 Número del documento: 0
Exportando: 3 Número del documento: 0
Exportando: 2 Número del documento: 0
Exportando: 1 Número del documento: 0
Exportando: 3 Número del documento: 1
Exportando: 0 Número del documento: 0
Exportando: 6 Número del documento: 2
etc...
Enviando correo electrónico de que los documentos están listos
respondido

Incluso mientras el WaitGroup está en ExportService y no devuelve nada al controlador, la respuesta aún espera que se complete la generación antes de responder con el mensaje de éxito.

Quiero decir que entiendo la concurrencia en Go, pero evidentemente me falta algo. ¿Alguien puede ayudarme?

Tags:  , , , ,

Answer

  1. Avatar for davy.ai

    Parece que la implementación actual de ExportService() está bloqueando el hilo principal hasta que se generen todos los documentos y se envíe el correo electrónico. Una solución a esto podría ser mover la llamada a ExportService() a una gorutina separada y hacer que devuelva un canal que envíe un mensaje cuando los documentos estén listos para ser enviados por correo electrónico.

    Aquí tienes un ejemplo:

    func generateDocx(n int, wg *sync.WaitGroup) {
        defer wg.Done()
        for i := 0; i < 10; i++ {
            amt := time.Duration(rand.Intn(250))
            time.Sleep(time.Millisecond * amt)
            fmt.Println("Exportar: ", n, "Documento número:", i)
        }
    }
    
    func ExportService(done chan<- bool) {
        defer close(done)
    
        wg := &sync.WaitGroup{}
        for i := 0; i < 10; i++ {
            wg.Add(1)
            go generateDocx(i, wg)
        }
        wg.Wait()
    
        fmt.Println("Enviando correo electrónico indicando que los documentos están listos")
        done <- true
    }
    
    func main() {
    
        finish := make(chan bool)
    
        router := mux.NewRouter()
    
        type Reply struct {
            Title   string
            Message string
        }
    
        router.HandleFunc("/exportar", func(w http.ResponseWriter, r *http.Request) {
            w.Header().Set("Content-Type", "application/json")
            w.WriteHeader(http.StatusCreated)
            fmt.Println("comenzando generación")
    
            done := make(chan bool)
            go ExportService(done)
    
            json.NewEncoder(w).Encode(&Reply{
                Title:   "Generando",
                Message: "Se están generando los archivos. Recibirás un correo electrónico cuando los archivos estén listos.",
            })
    
            go func() {
                <-done
                fmt.Println("Todos los documentos están listos, enviando correo electrónico al usuario.")
            }()
    
            fmt.Println("respondido")
        })
        log.Fatal(http.ListenAndServe(":8080", router))
        <-finish
    }
    

    En este ejemplo, creamos un nuevo canal done y se lo pasamos a ExportService() como argumento. Dentro de ExportService(), creamos un nuevo grupo de espera wg y agregamos las gorutinas para generar los documentos. Cuando se generan todos los documentos, enviamos un mensaje en el canal done para indicar que los documentos están listos para ser enviados por correo electrónico.

    De vuelta en el manejador HTTP, creamos una nueva gorutina para esperar a que el canal done reciba un mensaje que indique que los documentos están listos. Una vez recibido, podemos enviar un correo electrónico al usuario. Mientras tanto, el manejador responde de inmediato con un mensaje de éxito informando al usuario que los documentos se están generando.

    Al utilizar canales, podemos hacer que la generación de los documentos y el envío del correo electrónico ocurran de forma concurrente sin bloquear el hilo principal, lo cual permite una mejor experiencia de usuario.

Comments are closed.