如何使用time.After()代替time.Sleep()来实现可中断的暂停。

huangapple go评论80阅读模式
英文:

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

你可以在 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 分钟的睡眠时间。延迟退出那么长时间会使程序看起来非常不响应。

从这个答案中,我了解到可以使用 time.After() 创建一个可以被中断的循环延迟。

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

英文:

I have a program which periodically checks an external mailbox for messages and which has a user view which allows them to view messages and to terminate the program.

Stripped to minimal features it looks like this

package main

import (
	&quot;log&quot;
	&quot;time&quot;
)

func main() {
	log.Println(&quot;Hello, playground&quot;)

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

	go func() {
		for {
			select {
			case &lt;-quit:
				log.Println(&quot;Quitting&quot;)
				close(data)
				return
			case data &lt;- 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 &quot;quit&quot; button
		quit &lt;- true
	}()

loop:
	for {
		select {
		case info, ok := &lt;-data:
			if !ok {
				break loop
			}
			log.Println(&quot;Fetched&quot;, info)
		}
	}

	log.Println(&quot;Goodbye, playground&quot;)
}

func fetch() string {
	log.Println(&quot;Fetching&quot;)
	return &quot;message&quot;
}

You can run this in the Go Playground

Output is

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

Notice that

  • &quot;23:00:15 Fetching&quot; is something I didn't expect.
  • The program quits at 23:00:15, not at 23:00:12 because it's sleeping.

The latter would be a problem in my program because it uses a 10 minute sleep between checking for messages. Delaying a quit for that long would make the program seem pretty unresponsive.

From this answer I have learned that you can use time.After() to create a loop delay that can be interrupted.

How should I best apply that to my program?

答案1

得分: 3

「23:00:15 Fetching」是我没有预料到的事情。

这并不令人惊讶,这是预期的工作方式。引用自规范:选择语句

执行「select」语句有几个步骤:

  1. 对于语句中的所有情况,接收操作的通道操作数以及发送语句的通道和右侧表达式在进入「select」语句时按照源代码顺序进行一次性评估。
  2. [...]

因此,在决定执行哪个分支之前,「select」语句会评估通信操作。

这意味着:

case data <- fetch():

即使在 data 上的发送操作不可行,甚至在从 quit 接收操作可以立即进行的情况下,也会调用 fetch()

由于你在其中一个 case 分支中使用了 sleep,所以 quit 变为可接收状态并不重要,检查它(并可选择决定继续执行该分支)必须等待 time.Sleep() 完成,以及整个 case 分支。

因此,通信操作应该是从 time.After() 返回的通道进行接收操作,并且只在该 case 分支的主体中调用 fetch()

你可以这样使其工作:

for {
    // 等待直到整点的 0,10,20,30,40 或 50 分钟
    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
英文:

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

This is not surprising, this is the intended working. Quoting from 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.
>
> [...]

So the select statement evaluates the communication operations before it decides on which branch to proceed / execute.

This means that

case data &lt;- fetch():

fetch() will be called, even if sending on data would not be possible and even if receiving from quit can proceed immediately.

Since you have the sleep in one of the case branches, it doesn't matter that quit becomes ready to receive from, checking that (and optionally deciding to go on that branch) has to wait until time.Sleep() –and the whole case branch– completes.

So the communication operation should be a receive operation from the channel returned by time.After(), and only call fetch() in the body of this case branch.

You can make it work like this:

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 &lt;-quit:
        log.Println(&quot;Quitting&quot;)
        close(data)
        return
    case &lt;-time.After(delay):
        data &lt;- fetch()
    }
}

And now the output (try it on the 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

huangapple
  • 本文由 发表于 2017年5月10日 19:36:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/43891350.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定