Apakah ada cara untuk melakukan tugas yang berulang pada interval?

149

Apakah ada cara untuk melakukan tugas latar belakang berulang di Go? Saya sedang memikirkan sesuatu seperti Timer.schedule(task, delay, period)di Jawa. Saya tahu saya bisa melakukan ini dengan goroutine dan Time.sleep(), tetapi saya ingin sesuatu yang mudah dihentikan.

Inilah yang saya dapatkan, tetapi terlihat jelek bagi saya. Apakah ada cara yang lebih bersih / lebih baik?

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
sumber
3
Terima kasih telah menggunakan waktu. Durasi (x) dalam contoh Anda. Setiap contoh saya dapat menemukan memiliki hardcoded int dan mengeluh ketika Anda menggunakan int (atau float) vars.
Mike Graf
@ MikeGraf dapat Anda lakukan di t := time.Tick(time.Duration(period) * time.Second)mana periode adalahint
florianrosenberg
solusi ini sepertinya cukup bagus, IMO. esp. jika Anda cukup memanggil f () alih-alih di luar waktu. bagus untuk kasus di mana Anda ingin melakukan pekerjaan x detik setelah pekerjaan selesai, vs pada interval yang konsisten.
Luke W

Jawaban:

240

Fungsi time.NewTickermembuat saluran yang mengirim pesan berkala, dan menyediakan cara untuk menghentikannya. Gunakan sesuatu seperti ini (belum diuji):

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

Anda dapat menghentikan pekerja dengan menutup quitsaluran: close(quit).

Paul Hankin
sumber
9
Jawabannya salah tergantung pada apa yang diinginkan OP. Jika OP menginginkan eksekusi berkala terlepas dari berapa lama waktu yang digunakan pekerja, Anda harus menjalankan do stuffrutinitas go atau pekerja berikutnya akan segera mengeksekusi (ketika membutuhkan lebih dari 5 detik).
nemo
2
IMO, Anda harus tepat close(quit)ketika Anda ingin menghentikan penjadwal.
Dustin
3
Menghentikan ticker bekerja, tetapi goroutine tidak akan pernah menjadi sampah yang dikumpulkan.
Paul Hankin
4
@SteveBrisk Lihat dokumen . Jika saluran ditutup, pembacaan hanya akan berhasil dan Anda mungkin tidak menginginkannya.
nemo
10
@ bk0, saluran waktu tidak "mencadangkan" (dokumentasi mengatakan "Ini menyesuaikan interval atau menghilangkan kutu untuk mengganti penerima yang lambat"). Kode yang diberikan melakukan apa yang Anda katakan (menjalankan paling banyak satu tugas); jika tugas itu memakan waktu lama doa berikutnya hanya akan ditunda; tidak diperlukan mutex. Jika sebaliknya diinginkan bahwa tugas baru dimulai setiap interval (bahkan jika sebelumnya tidak selesai) maka gunakan saja go func() { /*do stuff */ }().
Dave C
26

Bagaimana dengan sesuatu seperti

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

Tempat bermain

Volker
sumber
3
A time.Tickerlebih baik daripada di time.Aftermana Anda lebih suka untuk menjaga tugas sesuai jadwal vs kesenjangan sewenang-wenang antara eksekusi.
Dustin
5
@ Dustin Dan ini lebih baik jika Anda ingin melakukan pekerjaan dengan celah tetap antara akhir dan awal tugas. Tidak ada yang terbaik - itu adalah dua kasus penggunaan yang berbeda.
no
`` `// Setelah menunggu durasi berlalu dan kemudian mengirim waktu saat ini // pada saluran yang dikembalikan. // Ini setara dengan NewTimer (d) .C. // Timer yang mendasarinya tidak dipulihkan oleh pengumpul sampah // sampai timer menyala. Jika efisiensi menjadi masalah, gunakan NewTimer `` `Bagaimana dengan pernyataan ini:If efficiency is a concern, use NewTimer
lee
23

Jika Anda tidak peduli dengan perubahan centang (tergantung pada berapa lama yang diperlukan sebelumnya pada setiap eksekusi) dan Anda tidak ingin menggunakan saluran, dimungkinkan untuk menggunakan fungsi rentang asli.

yaitu

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

Tempat bermain

Alekc
sumber
19

Lihat perpustakaan ini: https://github.com/robfig/cron

Contoh seperti di bawah ini:

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

Jawaban yang lebih luas untuk pertanyaan ini mungkin mempertimbangkan pendekatan bata Lego yang sering digunakan di Occam, dan ditawarkan kepada komunitas Jawa melalui JCSP . Ada presentasi yang sangat bagus dari Peter Welch tentang ide ini.

Pendekatan plug-and-play ini diterjemahkan langsung ke Go, karena Go menggunakan fundamental Proses Berkomunikasi yang sama seperti halnya Occam.

Jadi, ketika datang untuk merancang tugas berulang, Anda dapat membangun sistem Anda sebagai jaringan aliran data komponen sederhana (sebagai goroutine) yang bertukar peristiwa (yaitu pesan atau sinyal) melalui saluran.

Pendekatan ini bersifat komposisional: setiap kelompok komponen kecil itu sendiri dapat berperilaku sebagai komponen yang lebih besar, ad infinitum. Ini bisa sangat kuat karena sistem konkuren kompleks dibuat dari batu bata yang mudah dimengerti.

Catatan Kaki: dalam presentasi Welch, ia menggunakan sintaks Occam untuk saluran, yaitu ! dan ? dan ini secara langsung berhubungan dengan ch <- dan <-ch in Go.

Rick-777
sumber
3

Saya menggunakan kode berikut:

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

Ini lebih sederhana dan berfungsi dengan baik untuk saya.

Gustavo Emmel
sumber
0

Jika Anda ingin menghentikannya kapan saja ticker

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

Jika Anda tidak ingin menghentikannya, centang :

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