Golang中Context cancel()未生效的问题

Golang中Context cancel()未生效的问题 我按照Francesc(justforfunc)关于使用上下文进行简单取消的示例操作,但实际上并没有取消执行?

以下是代码:

package main

import (
	"context"
	"fmt"
	"time"
)

func sleepAndTalk(ctx context.Context, t time.Duration, txt string) {
	time.Sleep(t)
	fmt.Println(txt)
}

func main() {

	ctx := context.Background()
	ctx, cancel := context.WithCancel(ctx)

	go func() {

		time.Sleep(time.Second)
		cancel()
	}()

	sleepAndTalk(ctx, 5*time.Second, "yolo")

}

理论上,执行应该在一秒后被取消,但实际上并没有,而是继续等待5秒然后输出文本 yolo

在Go Playground上似乎也无法正常工作,有谁知道为什么这不起作用吗? https://play.golang.org/p/edsDZkYylrF

谢谢!


更多关于Golang中Context cancel()未生效的问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

好的,原来我只是不够耐心,它(还)不能正常工作的原因稍后会有解释 😉。

更多关于Golang中Context cancel()未生效的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


问题在于你的 sleepAndTalk 函数没有检查 ctx.Done() 通道,因此即使上下文被取消,函数仍然会继续执行完整的睡眠和输出操作。context.WithCancel 本身不会自动中断正在执行的函数,你需要显式地检查上下文的取消状态。

以下是修正后的代码:

package main

import (
	"context"
	"fmt"
	"time"
)

func sleepAndTalk(ctx context.Context, t time.Duration, txt string) {
	select {
	case <-time.After(t):
		fmt.Println(txt)
	case <-ctx.Done():
		fmt.Println("Cancelled:", ctx.Err())
	}
}

func main() {
	ctx := context.Background()
	ctx, cancel := context.WithCancel(ctx)

	go func() {
		time.Sleep(time.Second)
		cancel()
	}()

	sleepAndTalk(ctx, 5*time.Second, "yolo")
}

在这个版本中:

  • 使用 select 语句同时等待两个通道:time.After(t)ctx.Done()
  • 如果上下文在超时前被取消,ctx.Done() 通道会关闭,执行取消分支
  • 如果超时先发生,则正常输出文本

运行这个版本,你会看到一秒后输出 “Cancelled: context canceled” 而不是等待5秒后输出 “yolo”。

如果你需要在睡眠期间定期检查取消状态,可以使用以下替代实现:

func sleepAndTalk(ctx context.Context, t time.Duration, txt string) {
	for elapsed := time.Duration(0); elapsed < t; elapsed += time.Millisecond * 100 {
		select {
		case <-ctx.Done():
			fmt.Println("Cancelled:", ctx.Err())
			return
		default:
			time.Sleep(time.Millisecond * 100)
		}
	}
	fmt.Println(txt)
}

这个版本每100毫秒检查一次取消状态,提供更及时的响应。

回到顶部