首页 > 解决方案 > 使用上下文在连续的函数调用之间共享一个公共超时

问题描述

我想在 Go 程序中进行一些连续的函数调用,例如

(显然省略了错误检查)

result1, err := fxn1()

result2, err := fxn2()

我希望整个调用序列在一定时间内完成,否则应该中止该过程(并且程序退出错误)

假设我有超时(作为持续时间)以某种方式传递到我的程序中,我想一种方法是

a)创建一个带有截止日期的上下文,将超时添加到当前时间

myDeadline := time.Now().Add(timeout * time.Second)
ctx, cancel := context.WithDeadline(context.Background(), myDeadline)
defer cancel()

b)让最终函数通过通道传达其结果(我认为这称为通道生成器模式?)

func fxn2() (chan string) {

 resChan := make(chan string)

   go func() {
   // get the job done
   resChan <- result
   }()
 return resChan
}

c ) 然后在 select 语句上阻塞主 goroutine,如下所示

select {
  case err := <-ctx.Done():
    log.Println("Process has timed out...exiting")
    os.Exit(err)
  case result := <- resChan
    log.Println("Process completed in time")
}

这是我现在能想到的最好的方法,但我在徘徊是否有更好或更惯用的方式(比如为每个函数生成新的上下文副本 - 当然应该接受context.Context作为输入参数)以某种方式跟踪剩余时间?)

标签: gogo-context

解决方案


这比它需要的要复杂得多。有两种方法可以做到这一点:

  1. 如果函数带context.Context参数,则无需<-ctx.Done()在顶层等待。相反,当上下文完成时,函数将(在一些未知的未知延迟之后,可能永远不会,取决于函数)返回ctx.Err()(应该是context.DeadlineExceeded)。

  2. 如果函数不带context.Context参数,那么您可以退出该过程。你不需context.Context要这样做。我的意思是,这context.Context只是一种更复杂的调用方式<-time.After(timeout)

type myResultType struct{}

func longFunctionCall() (*myResultType, error) {
    ...
}

func invokeFunctionWithDeadline() {
    const timeout = 10 * time.Second

    // Run the function on a goroutine, and return the result with a channel.
    type resultStruct struct {
        value *myResultType
        err error
    }
    ch := make(chan resultStruct)
    go func() {
        v, err := longFunctionCall()
        ch <- resultStruct{v, err}
    }()

    // Wait for either the timeout, or the result.
    select {
    case <-time.After(timeout):
        log.Println("Timed out")
        os.Exit(1)
    case result := <-ch:
        log.Println("Success:", result)
    }
}

请注意,此策略仅在您调用 时才有效os.Exit(),这会强制终止所有未完成的 goroutine。


推荐阅读