Existe uma maneira de executar tarefas repetitivas em intervalos?

148

Existe uma maneira de executar tarefas repetitivas em segundo plano no Go? Estou pensando em algo como Timer.schedule(task, delay, period)em Java. Eu sei que posso fazer isso com uma goroutine e Time.sleep(), mas eu gostaria de algo que parasse facilmente.

Aqui está o que eu consegui, mas me parece feio. Existe uma maneira mais limpa / melhor?

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
fonte
3
Obrigado por usar o time.Duration (x) no seu exemplo. Cada exemplo que eu pude encontrar tem um int codificado e ele reclama quando você usa um int (ou float) vars.
Mike Graf
@MikeGraf você pode fazer t := time.Tick(time.Duration(period) * time.Second)onde o período é umint
florianrosenberg
esta solução parece muito boa, IMO. esp. se você simplesmente chamar f () em vez de fora do time.AfterFunc. ótimo para casos em que você deseja fazer o trabalho x segundos após o término do trabalho, em comparação com um intervalo consistente.
Luke W

Respostas:

240

A função time.NewTickercria um canal que envia uma mensagem periódica e fornece uma maneira de pará-lo. Use algo assim (não testado):

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

Você pode parar o trabalhador fechando o quitcanal: close(quit).

Paul Hankin
fonte
9
A resposta está incorreta, dependendo do que o OP deseja. Se o OP desejar uma execução periódica, independentemente de quanto tempo o trabalhador gaste, você precisará executar do stuffuma rotina de execução ou o próximo trabalhador executará imediatamente (quando precisar de mais de 5 segundos).
nemo
2
IMO, você deve exatamente close(quit)quando deseja parar o agendador.
Dustin
3
Parar o relógio funciona, mas a goroutine nunca será coletada como lixo.
Paul Hankin
4
@SteveBrisk Veja o documento . Se o canal estiver fechado, uma leitura seria bem-sucedida e talvez você não queira.
nemo
10
@ bk0, os canais de tempo não "fazem backup" (a documentação diz "Ajusta os intervalos ou descarta os ticks para compensar os receptores lentos"). O código fornecido faz exatamente o que você diz (executa no máximo uma tarefa); se a tarefa demorar muito, a próxima chamada será atrasada; nenhum mutex necessário. Se, em vez disso, desejar que uma nova tarefa seja iniciada a cada intervalo (mesmo que a anterior não tenha sido concluída), basta usar go func() { /*do stuff */ }().
Dave C
26

Que 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")
}

Parque infantil

Volker
fonte
3
A time.Tickeré melhor do que time.Afteronde você prefere manter a tarefa dentro do cronograma versus um intervalo arbitrário entre as execuções.
Dustin
5
@Dustin E isso é melhor se você deseja executar um trabalho com um intervalo fixo entre o final e o início das tarefas. Nem é o melhor - são dois casos de uso diferentes.
Nos
`` // Depois de aguardar a duração, envia a hora atual // no canal retornado. // É equivalente a NewTimer (d) .C. // O Timer subjacente não é recuperado pelo coletor de lixo // até que o timer seja disparado. Se a eficiência for uma preocupação, use o NewTimer `` Que tal esta declaração:If efficiency is a concern, use NewTimer
lee
23

Se você não se importa com a troca de marcações (dependendo de quanto tempo demorou anteriormente em cada execução) e não deseja usar canais, é possível usar a função de intervalo nativo.

ie

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")
    }
}

Parque infantil

Alekc
fonte
19

Confira esta biblioteca: https://github.com/robfig/cron

Exemplo como abaixo:

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
fonte
3

Uma resposta mais ampla a essa pergunta pode considerar a abordagem de blocos de Lego frequentemente usada no Occam e oferecida à comunidade Java via JCSP . Há uma apresentação muito boa de Peter Welch sobre essa idéia.

Essa abordagem plug-and-play se traduz diretamente no Go, porque o Go usa os mesmos fundamentos do Processo Seqüencial de Comunicação que o Occam.

Portanto, quando se trata de projetar tarefas repetitivas, você pode construir seu sistema como uma rede de fluxo de dados de componentes simples (como goroutines) que trocam eventos (ou seja, mensagens ou sinais) por meio de canais.

Essa abordagem é composicional: cada grupo de pequenos componentes pode se comportar como um componente maior, ad infinitum. Isso pode ser muito poderoso, porque sistemas concorrentes complexos são feitos de tijolos fáceis de entender.

Nota de rodapé: na apresentação de Welch, ele usa a sintaxe Occam para canais, o que é ! e ? e estes correspondem diretamente a ch <- e <-ch em Go.

Rick-777
fonte
3

Eu uso o seguinte 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")
}

É mais simples e funciona bem para mim.

Gustavo Emmel
fonte
0

Se você quiser pará-lo em qualquer momento ticker

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

Se você não quiser parar, marque :

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