在Golang中使用上下文超时停止运行功能

时间:2020-04-05 11:51:57

标签: go goroutine

我想利用golang中的上下文在达到超时时用于取消操作。

代码:

package main

import "fmt"
import "time"
import "context"

func F(ctx context.Context) error {
  ctx, cancel := context.WithTimeout(ctx,3*time.Second)
  defer cancel()
  for i:=0;i<10;i++ {
    time.Sleep(1 * time.Second)
    fmt.Println("No: ",i)
  }
  select {
    case <-ctx.Done():
      fmt.Println("TIME OUT")
      cancel()
      return ctx.Err()
    default:
      fmt.Println("ALL DONE")
      return nil
  }
}

func main() {
  ctx := context.Background()
  err := F(ctx)
  if err != nil {
    fmt.Println(err)
  }else {
    fmt.Println("Success")
  }
}

期望: 上面的代码应停止在计数器2处运行循环,因为超时为3秒,而循环每次运行1秒。所以我希望这样:

No:  0
No:  1
No:  2
TIME OUT
context deadline exceeded

实际: 实际发生的是,即使上下文遇到超时,并且选择侦听器在<-ctx.Done()上捕获了循环,循环仍将继续运行直到完成。这段代码显示:

No:  0
No:  1
No:  2
No:  3
No:  4
No:  5
No:  6
No:  7
No:  8
No:  9
TIME OUT
context deadline exceeded

如何在超时会议结束后停止函数执行?

1 个答案:

答案 0 :(得分:1)

context.Context只能中继发生超时或取消的消息。它无权实际停止任何goroutine(有关详细信息,请参见cancel a blocking operation in Go)。 goroutine本身负责检查超时和取消,并提前中止。

您有一个循环,该循环无条件地重复10次并打印出一些内容。而且,您仅在循环后检查超时。

您必须将上下文检查移入循环:

func F(ctx context.Context) error {
    ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
    defer cancel()
    for i := 0; i < 10; i++ {
        select {
        case <-ctx.Done():
            fmt.Println("TIME OUT")
            cancel()
            return ctx.Err()
        default:
            time.Sleep(1 * time.Second)
            fmt.Println("No: ", i)
        }
    }
    fmt.Println("ALL DONE")
    return nil
}

进行此更改后,输出将是(在Go Playground上尝试):

No:  0
No:  1
No:  2
No:  3
TIME OUT
context deadline exceeded

注意:是否看到"No: 3"的打印可能会或可能不会发生,因为任何迭代都需要1秒,并且超时是3秒= 3 *迭代延迟,所以超时是首先发生还是第4次迭代是“文明”。如果您将超时减少到2900 ms,"No: 3"将不会被打印。