Home > Software engineering >  How to repetitively run a function until it returns true or timeout?
How to repetitively run a function until it returns true or timeout?

Time:01-21

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

  •  Tags:  
  • Related