¿Hay alguna manera de hacer tareas repetitivas a intervalos?

148

¿Hay alguna manera de hacer tareas repetitivas en segundo plano en Go? Estoy pensando en algo como Timer.schedule(task, delay, period)en Java. Sé que puedo hacer esto con una gorutina y Time.sleep(), pero me gustaría algo que se detuviera fácilmente.

Esto es lo que obtuve, pero me parece feo. ¿Hay una forma más limpia / mejor?

func oneWay() {
    var f func()
    var t *time.Timer

    f = func () {
        fmt.Println("doing stuff")
        t = time.AfterFunc(time.Duration(5) * time.Second, f)
    }

    t = time.AfterFunc(time.Duration(5) * time.Second, f)

    defer t.Stop()

    //simulate doing stuff
    time.Sleep(time.Minute)
}
Steve Brisk
fuente
3
Gracias por usar time.Duration (x) en tu ejemplo. Cada ejemplo que pude encontrar tiene un int codificado y se queja cuando usas un int (o flotante) vars.
Mike Graf
@MikeGraf puedes hacer t := time.Tick(time.Duration(period) * time.Second)donde el período es unint
florianrosenberg
Esta solución parece bastante buena, en mi opinión. especialmente si simplemente llama f () en lugar del tiempo externo. AfterFunc. ideal para casos en los que desea hacer un trabajo x segundos después de realizado el trabajo, en comparación con un intervalo constante.
Luke W

Respuestas:

240

La función time.NewTickercrea un canal que envía un mensaje periódico y proporciona una forma de detenerlo. Úselo algo como esto (no probado):

ticker := time.NewTicker(5 * time.Second)
quit := make(chan struct{})
go func() {
    for {
       select {
        case <- ticker.C:
            // do stuff
        case <- quit:
            ticker.Stop()
            return
        }
    }
 }()

Puede detener el trabajador mediante el cierre del quitcanal: close(quit).

Paul Hankin
fuente
9
La respuesta es incorrecta dependiendo de lo que quiera el OP. Si el OP desea una ejecución periódica, independientemente de cuánto tiempo use el trabajador, deberá ejecutar do stuffuna rutina de inicio o, de lo contrario, el siguiente trabajador se ejecutará de inmediato (cuando necesite más de 5 segundos).
nemo
2
OMI, deberías solo close(quit)cuando quieras detener el programador.
Dustin
3
Detener el ticker funciona, pero nunca se recolectará basura.
Paul Hankin
44
@SteveBrisk Ver el documento . Si el canal está cerrado, una lectura solo tendría éxito y es posible que no desee eso.
nemo
10
@ bk0, los canales de tiempo no "hacen una copia de seguridad" (la documentación dice "Ajusta los intervalos o suelta las marcas para compensar los receptores lentos"). El código dado hace exactamente lo que usted dice (ejecuta como máximo una tarea); si la tarea lleva mucho tiempo, la próxima invocación simplemente se retrasará; No se requiere mutex. Si, en cambio, se desea que se inicie una nueva tarea cada intervalo (incluso si la anterior no está terminada), simplemente úsela go func() { /*do stuff */ }().
Dave C
26

¿Qué tal algo como

package main

import (
    "fmt"
    "time"
)

func schedule(what func(), delay time.Duration) chan bool {
    stop := make(chan bool)

    go func() {
        for {
            what()
            select {
            case <-time.After(delay):
            case <-stop:
                return
            }
        }
    }()

    return stop
}

func main() {
    ping := func() { fmt.Println("#") }

    stop := schedule(ping, 5*time.Millisecond)
    time.Sleep(25 * time.Millisecond)
    stop <- true
    time.Sleep(25 * time.Millisecond)

    fmt.Println("Done")
}

Patio de recreo

Volker
fuente
3
A time.Tickeres mejor que time.Afterdonde preferiría mantener la tarea a tiempo frente a una brecha arbitraria entre ejecuciones.
Dustin
55
@Dustin Y esto es mejor si quieres realizar un trabajo con un espacio fijo entre el final y el comienzo de las tareas. Ninguno de los dos es lo mejor: son dos casos de uso diferentes.
nos
`` `// Después espera a que transcurra la duración y luego envía la hora actual // en el canal devuelto. // Es equivalente a NewTimer (d) .C. // El recolector de basura no recupera el temporizador subyacente // hasta que se dispara el temporizador. Si la eficiencia es una preocupación, use NewTimer `` `` ¿Qué tal esta declaración:If efficiency is a concern, use NewTimer
lee
23

Si no le importa el cambio de tick (dependiendo de cuánto tiempo tardó previamente en cada ejecución) y no desea usar canales, es posible usar la función de rango nativo.

es decir

package main

import "fmt"
import "time"

func main() {
    go heartBeat()
    time.Sleep(time.Second * 5)
}

func heartBeat() {
    for range time.Tick(time.Second * 1) {
        fmt.Println("Foo")
    }
}

Patio de recreo

Alekc
fuente
19

Echa un vistazo a esta biblioteca: https://github.com/robfig/cron

Ejemplo como a continuación:

c := cron.New()
c.AddFunc("0 30 * * * *", func() { fmt.Println("Every hour on the half hour") })
c.AddFunc("@hourly",      func() { fmt.Println("Every hour") })
c.AddFunc("@every 1h30m", func() { fmt.Println("Every hour thirty") })
c.Start()
Browny Lin
fuente
3

Una respuesta más amplia a esta pregunta podría considerar el enfoque de bloques de Lego que se usa a menudo en Occam y se ofrece a la comunidad Java a través de JCSP . Hay una muy buena presentación de Peter Welch sobre esta idea.

Este enfoque plug-and-play se traduce directamente en Go, porque Go usa los mismos fundamentos del Proceso secuencial de comunicación que Occam.

Entonces, cuando se trata de diseñar tareas repetitivas, puede construir su sistema como una red de flujo de datos de componentes simples (como gorutinas) que intercambian eventos (es decir, mensajes o señales) a través de canales.

Este enfoque es compositivo: cada grupo de componentes pequeños puede comportarse como un componente más grande, hasta el infinito. Esto puede ser muy poderoso porque los sistemas concurrentes complejos están hechos de ladrillos fáciles de entender.

Nota al pie: en la presentación de Welch, usa la sintaxis de Occam para los canales, ¡lo cual es ! y ? y estos corresponden directamente a ch <- y <-ch en Go.

Rick-777
fuente
3

Yo uso el siguiente código:

package main

import (
    "fmt"
    "time"
)

func main() {
    now := time.Now()
    fmt.Println("\nToday:", now)

    after := now.Add(1 * time.Minute)
    fmt.Println("\nAdd 1 Minute:", after)

    for {
        fmt.Println("test")
        time.Sleep(10 * time.Second)

        now = time.Now()

        if now.After(after) {
            break
        }
    }

    fmt.Println("done")
}

Es más simple y funciona bien para mí.

Gustavo Emmel
fuente
0

Si quieres detenerlo en cualquier momento ticker

ticker := time.NewTicker(500 * time.Millisecond)
go func() {
    for range ticker.C {
        fmt.Println("Tick")
    }
}()
time.Sleep(1600 * time.Millisecond)
ticker.Stop()

Si no desea detenerlo, marque :

tick := time.Tick(500 * time.Millisecond)
for range tick {
    fmt.Println("Tick")
}
John Balvin Arias
fuente