如何使用 time.After() 而不是 time.Sleep() 来获得可中断的暂停

How to use time.After() instead of time.Sleep() to obtain interruptible pause

我有一个程序定期检查外部邮箱的消息,并且有一个用户视图,允许他们查看消息并终止程序。

剥离到最小的功能,看起来像这样

package main

import (
    "log"
    "time"
)

func main() {
    log.Println("Hello, playground")

    quit := make(chan bool)
    data := make(chan string)

    go func() {
        for {
            select {
            case <-quit:
                log.Println("Quitting")
                close(data)
                return
            case data <- fetch():
                // Wait until e.g. exactly 0,10,20,30,40 or 50 mins past the hour
                interval := time.Second * 5
                now := time.Now()
                time.Sleep(now.Truncate(interval).Add(interval).Sub(now))
            }
        }
    }()

    go func() {
        time.Sleep(12 * time.Second) // actually user presses a "quit" button
        quit <- true
    }()

loop:
    for {
        select {
        case info, ok := <-data:
            if !ok {
                break loop
            }
            log.Println("Fetched", info)
        }
    }

    log.Println("Goodbye, playground")
}

func fetch() string {
    log.Println("Fetching")
    return "message"
}

你可以run this in the Go Playground

输出为

2009/11/10 23:00:00 Hello, playground
2009/11/10 23:00:00 Fetching
2009/11/10 23:00:00 Fetched message
2009/11/10 23:00:05 Fetching
2009/11/10 23:00:05 Fetched message
2009/11/10 23:00:10 Fetching
2009/11/10 23:00:10 Fetched message
2009/11/10 23:00:15 Fetching
2009/11/10 23:00:15 Quitting
2009/11/10 23:00:15 Goodbye, playground

注意

后者在我的程序中会是一个问题,因为它在检查消息之间使用 10 分钟的睡眠时间。延迟退出那么久会使程序看起来反应迟钝。

this answer 我了解到您可以使用 time.After() 创建一个可以中断的循环延迟。

我应该如何最好地将其应用到我的程序中?

"23:00:15 Fetching" is something I didn't expect.

这并不奇怪,这就是预期的工作方式。引用自 Spec: Select statements:

Execution of a "select" statement proceeds in several steps:

  1. For all the cases in the statement, the channel operands of receive operations and the channel and right-hand-side expressions of send statements are evaluated exactly once, in source order, upon entering the "select" statement.

[...]

因此 select 语句在决定继续/执行哪个分支之前评估通信操作。

这意味着

case data <- fetch():

fetch() 将被调用,即使在 data 上发送是不可能的,即使从 quit 接收可以立即继续。

由于您在 case 分支之一中睡眠,因此 quit 准备好接收、检查(并可选择决定继续该分支)并不重要必须等到 time.Sleep() – 整个 case 分支 – 完成。

所以通信操作应该是从time.After()返回的通道接收操作,并且只调用fetch()在这个case分支的body中。

你可以让它像这样工作:

for {
    // Wait until e.g. exactly 0,10,20,30,40 or 50 mins past the hour
    interval := time.Second * 5
    now := time.Now()
    delay := now.Truncate(interval).Add(interval).Sub(now)

    select {
    case <-quit:
        log.Println("Quitting")
        close(data)
        return
    case <-time.After(delay):
        data <- fetch()
    }
}

现在输出(在 Go Playground 上尝试):

2009/11/10 23:00:00 Hello, playground
2009/11/10 23:00:05 Fetching
2009/11/10 23:00:05 Fetched message
2009/11/10 23:00:10 Fetching
2009/11/10 23:00:10 Fetched message
2009/11/10 23:00:12 Quitting
2009/11/10 23:00:12 Goodbye, playground