在 Golang 中使用上下文超时停止正在运行的函数

Jak*_*ler 1 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")
  }
}
Run Code Online (Sandbox Code Playgroud)

期望:上面的代码应该在 counter 处停止运行循环2,因为超时为 3 秒,循环每次运行 1 秒。所以我期待这样的事情:

No:  0
No:  1
No:  2
TIME OUT
context deadline exceeded
Run Code Online (Sandbox Code Playgroud)

实际情况:实际发生的是循环继续运行直到完成,即使上下文满足超时并且选择侦听器捕获该情况<-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
Run Code Online (Sandbox Code Playgroud)

超时后如何停止函数执行?

icz*_*cza 8

context.Context只能转发发生超时或取消的消息。它没有能力实际停止任何 goroutine(有关详细信息,请参阅取消 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")
            return ctx.Err()
        default:
            time.Sleep(1 * time.Second)
            fmt.Println("No: ", i)
        }
    }
    fmt.Println("ALL DONE")
    return nil
}
Run Code Online (Sandbox Code Playgroud)

通过此更改,输出将是(在Go Playground上尝试):

No:  0
No:  1
No:  2
No:  3
TIME OUT
context deadline exceeded
Run Code Online (Sandbox Code Playgroud)

注意:无论您看到"No: 3"打印的内容是否可能发生,因为任何迭代都需要 1 秒,超时为 3 秒 = 3 * 迭代延迟,因此无论超时先发生还是第 4 次迭代先开始都是“有问题的”。如果将超时减少到 2900 毫秒,"No: 3"则不会打印。