英文:
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 <- some_channel:
return value
case <- 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
语句之外,并根据需要进行初始化。当 timeout
为 nil
时,将通道保持为 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<<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 <-some_channel
}
select {
case value := <-some_channel:
return value
case <-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 <-chan time.Time
if timeout != nil {
timeoutCh = time.After(*timeout)
}
select {
case value := <-some_channel:
return value
case <-timeoutCh:
return nil
}
}
答案2
得分: 5
你可以在函数中接受一个context.Context
,而不是使用持续时间,我认为这在Go代码中是相当惯用的做法。
然后,调用者可以根据需要使用Context.Background
或Context.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 <-some_channel:
return value
case <-ctx.Done():
return nil
}
}
答案3
得分: 3
首先,通常使用time.Duration
的0
(或负值)来表示没有超时 - 因此不需要传递指针。
其次,在强制执行超时或不超时时,只需检查这个零值:
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 <= 0 {
return <- some_channel
}
select {
case value <- some_channel:
return value
case <- time.After(timeout):
return nil
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论