Gibt es eine Möglichkeit, sich wiederholende Aufgaben in regelmäßigen Abständen auszuführen?


148

Gibt es eine Möglichkeit, sich wiederholende Hintergrundaufgaben in Go auszuführen? Ich denke an so etwas wie Timer.schedule(task, delay, period)in Java. Ich weiß, dass ich das mit einer Goroutine machen kann und Time.sleep(), aber ich möchte etwas, das leicht zu stoppen ist.

Folgendes habe ich bekommen, sehe aber für mich hässlich aus. Gibt es einen saubereren / besseren Weg?

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

3
Vielen Dank, dass Sie time.Duration (x) in Ihrem Beispiel verwendet haben. Jedes Beispiel, das ich finden konnte, hat ein fest codiertes int und es beschwert sich, wenn Sie ein int (oder float) vars verwenden.
Mike Graf

@ MikeGraf können Sie tun, t := time.Tick(time.Duration(period) * time.Second)wo Zeitraum ist einint
florianrosenberg

Diese Lösung scheint ziemlich gut zu sein, IMO. insb. Wenn Sie einfach f () anstelle der äußeren Zeit aufrufen. AfterFunc. Ideal für Fälle, in denen Sie x Sekunden nach Abschluss der Arbeit arbeiten möchten, im Vergleich zu einem konsistenten Intervall.
Luke W

Antworten:


240

Die Funktion erstellt time.NewTickereinen Kanal, der eine periodische Nachricht sendet, und bietet eine Möglichkeit, diese zu stoppen. Verwenden Sie es so etwas (ungetestet):

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

Sie können den Worker stoppen, indem Sie den quitKanal schließen : close(quit).


9
Die Antwort ist falsch, je nachdem, was das OP will. Wenn das OP eine regelmäßige Ausführung wünscht, unabhängig davon, wie viel Zeit der Worker benötigt, müssten Sie do stuffeine Go-Routine ausführen, oder der nächste Worker würde sofort ausgeführt (wenn er mehr als 5 Sekunden benötigt).
Nemo

2
IMO sollten Sie nur dann, close(quit)wenn Sie den Scheduler stoppen möchten.
Dustin

3
Das Stoppen des Tickers funktioniert, aber die Goroutine wird niemals Müll gesammelt.
Paul Hankin

4
@SteveBrisk Siehe Dokument . Wenn der Kanal geschlossen ist, ist ein Lesevorgang nur erfolgreich, und das möchten Sie möglicherweise nicht.
Nemo

10
@ bk0, Zeitkanäle "sichern" nicht (in der Dokumentation heißt es "Passt die Intervalle an oder lässt Ticks fallen, um langsame Empfänger auszugleichen"). Der angegebene Code macht genau das, was Sie sagen (führt höchstens eine Aufgabe aus); Wenn die Aufgabe lange dauert, wird der nächste Aufruf einfach verzögert. Kein Mutex erforderlich. Wenn stattdessen gewünscht wird, dass in jedem Intervall eine neue Aufgabe gestartet wird (auch wenn die vorherige nicht abgeschlossen ist), verwenden Sie einfach go func() { /*do stuff */ }().
Dave C

26

Wie wäre es mit so etwas

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

Spielplatz


3
A time.Tickerist besser als dort, time.Afterwo Sie die Aufgabe lieber im Zeitplan halten möchten, als eine willkürliche Lücke zwischen den Ausführungen.
Dustin

5
@Dustin Und das ist besser, wenn Sie Arbeiten mit einer festen Lücke zwischen dem Ende und dem Beginn der Aufgaben ausführen möchten. Beides ist nicht das Beste - es sind zwei verschiedene Anwendungsfälle.
Nr.

`` `// Nachdem gewartet wurde, bis die Dauer abgelaufen ist, und dann die aktuelle // Uhrzeit auf dem zurückgegebenen Kanal gesendet wird. // Es entspricht NewTimer (d) .C. // Der zugrunde liegende Timer wird vom Garbage Collector // erst wiederhergestellt, wenn der Timer ausgelöst wird. Wenn Effizienz ein Problem ist, verwenden Sie NewTimer `` `Wie wäre es mit dieser Aussage:If efficiency is a concern, use NewTimer
Lee

23

Wenn Sie sich nicht für das Tick-Shifting interessieren (abhängig davon, wie lange es zuvor bei jeder Ausführung gedauert hat) und keine Kanäle verwenden möchten, können Sie die native Bereichsfunktion verwenden.

dh

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

Spielplatz


19

Schauen Sie sich diese Bibliothek an: https://github.com/robfig/cron

Beispiel wie folgt:

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()

3

Eine umfassendere Antwort auf diese Frage könnte den in Occam häufig verwendeten Lego-Brick-Ansatz berücksichtigen, der der Java-Community über JCSP angeboten wird . Es gibt eine sehr gute Präsentation von Peter Welch zu dieser Idee.

Dieser Plug-and-Play-Ansatz wird direkt in Go übersetzt, da Go dieselben Grundlagen für den kommunizierenden sequentiellen Prozess verwendet wie Occam.

Wenn Sie sich wiederholende Aufgaben entwerfen, können Sie Ihr System als Datenflussnetzwerk aus einfachen Komponenten (als Goroutinen) aufbauen, die Ereignisse (dh Nachrichten oder Signale) über Kanäle austauschen.

Dieser Ansatz ist kompositorisch: Jede Gruppe kleiner Komponenten kann sich ad infinitum als größere Komponente verhalten. Dies kann sehr leistungsfähig sein, da komplexe gleichzeitige Systeme aus leicht verständlichen Bausteinen bestehen.

Fußnote: In Welchs Präsentation verwendet er die Occam-Syntax für Kanäle, das heißt ! und ? und diese entsprechen direkt ch <- und <-ch in Go.


3

Ich benutze den folgenden Code:

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 ist einfacher und funktioniert gut für mich.


0

Wenn Sie es in jedem Moment stoppen möchten, ticken Sie

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

Wenn Sie nicht aufhören wollen, es tick :

tick := time.Tick(500 * time.Millisecond)
for range tick {
    fmt.Println("Tick")
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.