如何使用 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
注意
"23:00:15 Fetching"
是我没想到的
- 程序在 23:00:15 时退出,而不是在 23:00:12 时退出,因为它正在休眠。
后者在我的程序中会是一个问题,因为它在检查消息之间使用 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:
- 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
我有一个程序定期检查外部邮箱的消息,并且有一个用户视图,允许他们查看消息并终止程序。
剥离到最小的功能,看起来像这样
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
注意
"23:00:15 Fetching"
是我没想到的- 程序在 23:00:15 时退出,而不是在 23:00:12 时退出,因为它正在休眠。
后者在我的程序中会是一个问题,因为它在检查消息之间使用 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:
- 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