使用上下文在连续的函数调用之间共享一个公共超时

Posted

技术标签:

【中文标题】使用上下文在连续的函数调用之间共享一个公共超时【英文标题】:Using context to share a common timeout across consecutive function calls 【发布时间】:2021-10-15 06:55:52 【问题描述】:

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

(显然省略了错误检查)

result1, err := fxn1()

result2, err := fxn2()

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

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

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

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 作为输入参数)以某种方式跟踪剩余时间?)

【问题讨论】:

通常您将上下文传递给需要对其执行操作的函数,但您在这里没有做任何事情 time.After 无法处理。我不知道您所说的“为每个函数生成新的上下文副本”是什么意思,所以我不确定您期望发生什么(并且您对截止日期和超时感到困惑,您的第一个示例将无法编译)。是的,跨多个函数调用共享上下文是正常的,但你到底要问什么? 我并不是建议您使用time.After,我只是说与您的上下文使用示例几乎没有区别,无论哪种方式您都会在超时后立即退出。你在这里的最终目标是什么? 如果您的目标只是从程序开始时设置一个截止日期,这将起作用,那么什么不符合您的预期? 您不需要他们分享任何内容,因为如果结果没有及时准备好,您可以丢弃它们。退出进程将终止剩余的协程。 您的示例在超时时不会中止作业,它将继续运行,然后忽略结果。如果这是您的意图,您可能会进一步简化它。 【参考方案1】:

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

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

    如果函数不接受context.Context 参数,那么您可以退出进程。您不需要context.Context 来执行此操作。我的意思是context.Context 只是调用&lt;-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 <- resultStructv, 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。

【讨论】:

这是一个不错的模式;考虑到我有longFunctionCall1longFunctionCall2、3、4...,我认为它不会有太大变化 是的,你可以把那些放在go func() ... 里面

以上是关于使用上下文在连续的函数调用之间共享一个公共超时的主要内容,如果未能解决你的问题,请参考以下文章

Qt中多个定时器共享一个槽函数,怎么确定是哪个定时器超时呢?

在连续的 java CompletionStages 之间对共享上下文对象所做的最新更改对于执行 lambda 的每个线程是不是始终可见

qt两个widget渲染相同

R中最长的公共子字符串在两个字符串之间找到不连续的匹配

无法使用反应上下文在不同组件之间共享数据

快速在视图之间传递和调用变量和函数