Есть ли способ делать повторяющиеся задачи через определенные промежутки времени?


149

Есть ли способ делать повторяющиеся фоновые задачи в Go? Я думаю о чем-то похожем Timer.schedule(task, delay, period)на Java. Я знаю, что могу сделать это с помощью рутины и Time.sleep(), но я хотел бы что-то, что легко остановилось.

Вот что я получил, но выглядит мне некрасиво. Есть ли чище / лучше?

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
Спасибо за использование time.Duration (x) в вашем примере. Каждый пример, который я могу найти, имеет жестко запрограммированный int, и он жалуется, когда вы используете int (или float).
Майк Граф

@MikeGraf вы можете делать, t := time.Tick(time.Duration(period) * time.Second)когда периодint
florianrosenberg

это решение кажется довольно хорошим, ИМО. особенно если вы просто вызываете f () вместо внешнего time.AfterFunc. отлично подходит для случаев, когда вы хотите сделать работу через x секунд после выполнения работы, а не через определенный интервал.
Люк W

Ответы:


240

Функция time.NewTickerсоздает канал, который отправляет периодическое сообщение, и предоставляет способ остановить его. Используйте это как-то так (не проверено):

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

Вы можете остановить рабочий, закрывая quitканал: close(quit).


9
Ответ неправильный в зависимости от того, что хочет ОП. Если OP хочет периодическое выполнение независимо от того, сколько времени работник использует, вам нужно будет выполнить do stuffпроцедуру go, иначе следующий рабочий выполнит немедленно (при необходимости более 5 секунд).
Немо

2
ИМО, вы должны именно close(quit)тогда, когда вы хотите остановить планировщик.
Дастин

3
Остановка тикера работает, но программа никогда не будет собирать мусор.
Пол Ханкин

4
@SteveBrisk Смотрите документацию . Если канал закрыт, чтение будет успешным, и вы можете этого не хотеть.
Немо

10
@ bk0, временные каналы не «резервируются» (в документации сказано: «Он устанавливает интервалы или сбрасывает тики, чтобы компенсировать медленные приемники»). Данный код делает именно то, что вы говорите (выполняет не более одной задачи); если задача занимает много времени, следующий вызов будет просто отложен; мьютекс не требуется. Если вместо этого желательно, чтобы новая задача запускалась каждый интервал (даже если предыдущая не завершена), просто используйте go func() { /*do stuff */ }().
Дейв С

26

Как насчет чего-то вроде

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

Игровая площадка


3
time.TickerЛучше , чем time.Afterгде вы предпочли бы сохранить задачу по графике против произвольного разрыва между казнями.
Дастин

5
@Dustin И это лучше, если вы хотите выполнять работу с фиксированным промежутком между концом и началом задач. Ни один из них не лучший - это два разных варианта использования.
NOS

`` `// After ожидает истечения продолжительности и затем отправляет текущее время // на возвращенный канал. // Это эквивалентно NewTimer (d) .C. // Базовый таймер не восстанавливается сборщиком мусора // до тех пор, пока не сработает таймер. Если эффективность является проблемой, используйте NewTimer `` `Как насчет этого утверждения:If efficiency is a concern, use NewTimer
Ли

23

Если вас не интересует смещение тиков (в зависимости от того, сколько времени это занимало ранее при каждом выполнении) и вы не хотите использовать каналы, можно использовать встроенную функцию диапазона.

т.е.

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

Игровая площадка


19

Проверьте эту библиотеку: https://github.com/robfig/cron

Пример как ниже:

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

Более широкий ответ на этот вопрос мог бы рассмотреть подход Lego Brick, часто используемый в Occam и предлагаемый сообществу Java через JCSP . По этой идее Питер Уэлч сделал очень хорошую презентацию .

Этот подход «подключи и работай» переводится непосредственно в Go, потому что Go использует те же самые основы последовательного процесса связи, что и Occam.

Таким образом, когда речь идет о разработке повторяющихся задач, вы можете построить свою систему как сеть потоков данных из простых компонентов (таких как программы), которые обмениваются событиями (например, сообщениями или сигналами) по каналам.

Этот подход композиционный: каждая группа небольших компонентов может вести себя как большой компонент до бесконечности. Это может быть очень мощным, потому что сложные параллельные системы сделаны из простых для понимания кирпичей.

Сноска: в презентации Уэлча он использует синтаксис Оккама для каналов, а это ! а ? и они прямо соответствуют ch <- и <-ch в Go.


3

Я использую следующий код:

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

Это более просто и прекрасно работает для меня.


0

Если вы хотите , чтобы остановить его в любой момент тикер

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

Если вы не хотите, чтобы остановить это, отметьте :

tick := time.Tick(500 * time.Millisecond)
for range tick {
    fmt.Println("Tick")
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.