有没有办法让time.After()函数无限期地等待?

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

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 /* 或其他值 */
    }

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

但我不知道是否有一种方法可以表示time.Forever

英文:

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

Motivation: I have a service that a caller can request a message from, with an optional timeout. The obvious way to do this would be:

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 &lt;- some_channel:
        return value
    case &lt;- time.After(timeout_value):
        return nil
    }
}

Except I don't know if there's a way to say time.Forever.

答案1

得分: 8

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

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

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

请注意,如果"永远"是期望的最大等待时间,那么省略 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
    }
}
英文:

There is no "forever" duration, but there is max duration:

const maxDuration time.Duration = 1&lt;&lt;63 - 1

maxDuration is about 292 years. It should be enough for the lifetime of a single app. But instead I propose the below solution which doesn't use it:

Note that if "forever" is the intended max wait time, it's simpler and more efficient to omit time.After() and use a simple receive:

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

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

You indicated that your actual code is much more complex and contains more cases.

In that case, I'd move the timeout channel creation outside of the select statement, and initialize accordingly. When timeout is nil, just leave the channel nil (its zero value), which will never deliver any value, so receiving from a nil channel literally takes "forever":

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

	select {
	case value := &lt;-some_channel:
		return value
	case &lt;-timeoutCh:
		return nil
	}
}

答案2

得分: 5

你可以在函数中接受一个context.Context,而不是使用持续时间,我认为这在Go代码中是相当惯用的做法。

然后,调用者可以根据需要使用Context.BackgroundContext.WithTimeout调用函数。service函数在上下文的Done()上进行选择,如果是后台上下文,它将永远不会结束(通道实际上为nil)。

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
    }
}
英文:

Instead of the duration you could accept a context.Context in your function, which I think is quite idiomatic in Go code.

Then the caller can call the function with a Context.Background or with a Context.WithTimeout as needed. The service function selects on the context's Done(), which in case of the background context never ends (the chan is effectively 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 &lt;-some_channel:
            return value
        case &lt;-ctx.Done():
            return nil
    }
}

答案3

得分: 3

首先,通常使用time.Duration0(或负值)来表示没有超时 - 因此不需要传递指针。

其次,在强制执行超时或不超时时,只需检查这个零值:

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
    }
}
英文:

Firstly, it's common practice to use a time.Duration of 0 (or negative) to indicate no timeout - so it's not necessary to pass a pointer.

Secondly, just check for this zero-value when enforcing a timeout or not:

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

    }

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

huangapple
  • 本文由 发表于 2021年8月12日 20:53:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/68757872.html
匿名

发表评论

匿名网友

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

确定