You cannot interrupt a time.Sleep()
.
Instead if you need to listen to other "events" while waiting for something, you should use channels and the select
statements. A select
is similar to a switch
, but with the cases all referring to communication operations.
The good thing of select
is that you may list multiple cases, all refering to comm. ops (e.g. channel receives or sends), and whichever operation can proceed will be chosen (choosing one randomly if multiple can proceed). If none can proceed, it will block (wait) until one of the comm. ops can proceed (unless there's a default
).
A "timeout" event may be realized using time.After()
, or a series of "continuous" timeout events (ticks or heartbeats) using time.Ticker
.
You should change your button handler to send a value (the button state) on a channel, so receiving from this channel can be added to the select
inside poll()
, and processed "immediately" (without waiting for a timeout or the next tick event).
See this example:
func poll(buttonCh <-chan bool) {
isDevice := false
read := func() {
if isDevice {
device1()
} else {
sensor1()
}
}
ticker := time.NewTicker(1 * time.Second)
defer func() { ticker.Stop() }()
for {
select {
case isDevice = <-buttonCh:
case <-ticker.C:
read()
}
}
}
func device1() { log.Println("reading device1") }
func sensor1() { log.Println("reading sensor1") }
Testing it:
buttonCh := make(chan bool)
// simulate a click after 2.5 seconds:
go func() {
time.Sleep(2500 * time.Millisecond)
buttonCh <- true
}()
go poll(buttonCh)
time.Sleep(5500 * time.Millisecond)
Output (try it on the Go Playground):
2009/11/10 23:00:01 reading sensor1
2009/11/10 23:00:02 reading sensor1
2009/11/10 23:00:03 reading device1
2009/11/10 23:00:04 reading device1
2009/11/10 23:00:05 reading device1
This solution makes it easy to add any number of event sources without changing anything. For example if you want to add a "shutdown" event and a "read-now" event to the above solution, this is how it would look like:
for {
select {
case isDevice = <-buttonCh:
case <-ticker.C:
read()
case <-readNowCh:
read()
case <-shutdownCh:
return
}
}
Where shutdownCh
is a channel with any element type, and to signal shutdown, you do that by closing it:
var shutdownCh = make(chan struct{})
// Somewhere in your app:
close(shutdownCh)
Closing the channel has the advantage that you may have any number of goroutines "monitoring" it, all will receive the shutdown (this is like broadcasting). Receive from a closed channel can proceed immediately, yielding the zero-value of the channel's element type.
To issue an "immediate read" action, you would send a value on readNowCh
.