有没有办法无限期地询问 time.After() ?

Is there a way to ask time.After() for an infinite amount of time?

有没有办法无限期地询问time.After()

动机:我有一个服务,呼叫者可以从中请求一条消息,有一个可选的超时。显而易见的方法是:

func service(timeout *time.Duration) SomeType {
    var timeout_value time.Duration
    if timeout != nil {
        timeout_value = *timeout
    } else {
        timeout_value = time.Forever /* or something */
    }

    select {
    case value <- some_channel:
        return value
    case <- time.After(timeout_value):
        return nil
    }
}

除了我不知道是否有办法说 time.Forever

没有“永远”的持续时间,但有最大持续时间:

const maxDuration time.Duration = 1<<63 - 1

maxDuration 大约是 292 年。对于单个应用程序的生命周期来说应该足够了。但相反,我提出了以下不使用它的解决方案:

请注意,如果“永远”是预期的最长等待时间,则省略 time.After() 并使用简单的接收更简单、更有效:

func service(timeout *time.Duration) SomeType {
    if timeout == nil {
        return <-some_channel
    }

    select {
    case value := <-some_channel:
        return value
    case <-time.After(*timeout):
        return nil
    }
}

您表示您的实际代码要复杂得多并且包含更多案例。

在那种情况下,我会将超时通道创建移到 select 语句之外,并进行相应的初始化。当 timeoutnil 时,只需离开通道 nil(它的零值),它将永远不会传递任何值,因此从 nil 通道接收字面意思是“永远” :

func service(timeout *time.Duration) SomeType {
    var timeoutCh <-chan time.Time
    if timeout != nil {
        timeoutCh = time.After(*timeout)
    }

    select {
    case value := <-some_channel:
        return value
    case <-timeoutCh:
        return nil
    }
}

首先,通常的做法是使用 0time.Duration(或负数)来指示没有超时 - 因此没有必要传递指针。

其次,在是否强制超时时检查这个零值:

func service(timeout time.Duration) SomeType {
    
    if timeout <= 0 {
        return <- some_channel

    }

    select {
        case value <- some_channel:
            return value
        case <- time.After(timeout):
            return nil
    }
}

你可以在你的函数中接受 context.Context 而不是持续时间,我认为这在 Go 代码中是非常惯用的。

然后调用者可以根据需要用Context.Background or with a Context.WithTimeout调用函数。 service 函数选择上下文的 Done(),在背景上下文永远不会结束的情况下(chan 实际上为 nil)。

Done may return nil if this context can never be canceled. [...] Done is provided for use in select statements

func callerNoTimeout() {
    foo := service(context.Background())
}

func callerTimeout() {
    foo := service(context.WithTimeout(context.Background(), timeOut))
}

func service(ctx context.Context) SomeType {
    select {
        case value <-some_channel:
            return value
        case <-ctx.Done():
            return nil
    }
}