I have a function checkSuccess() which return true if the task have finished.
I'd like to call checkSuccess() every 1 second and break until it return true or timeout.
What I'm having now is to use a goroutine to run a for loop, in which I call checkSuccess() every 1 second. And in the main process, I use time.After to check if the whole checking duration has passed timeout.
func myfunc (timeout time.Duration) {
successChan := make(chan struct{})
timeoutChan := make(chan struct{})
// Keep listening to the task.
go func() {
for {
select {
// Exit the forloop if the timeout has been reached
case <-timeoutChan:
return
default:
}
// Early exit if task has succeed.
if checkSuccess() {
close(successChan)
return
}
time.Sleep(time.Second)
}
}()
// Once timeout, stop listening to the task.
select {
case <-time.After(timeout):
close(timeoutChan)
return
case <-successChan:
return
}
return
}
It actually has achieved my goal, but I found it very tedious. Is there any better (shorter) way to write it?
CodePudding user response:
You don't need a separate goroutine or channels:
func myfunc (timeout time.Duration) {
ticker:=time.NewTicker(time.Second)
defer ticker.Close()
to:=time.NewTimer(timeout)
defer to.Stop()
for {
select {
case <-to.C:
return // timeout
case <-ticker:
if checkSuccess() {
return
}
}
}
}
CodePudding user response:
I prefer using context.Context in cases like this, so that the utility function can more easily be adapted to a variety of real world scenarios.
I would also return something, maybe error, e.g. from ctx.Err() or the bool, to give some feedback to the caller (the caller may decide to discard it anyway):
func tryFunc(ctx context.Context, f func() bool) bool {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return false
case <-ticker.C:
if b := f(); b {
return b
}
}
}
}
Playground: https://go.dev/play/p/Iz_urEkBMIi
PS: make sure the context has a timeout. context.Background() doesn't
