上一篇文章我们提到:
首先说下场景:
我们有时会遇到,在处理某些任务时,在外部可以对其及时终止,比如传输文件时。
所以我们的 Context 也提供了一个传递取消信号的方法,我们只需要调用这个方法,就会像监听在这个上下文的 Done 通道上的方法。
注意:这里不是直接干掉这个方法,仅仅只是传递一个信号。
范例代码如下:
func req3(ctx context.Context) {
// 监听取消信息
select {
case <-ctx.Done():
fmt.Println("收到取消信号了")
return
}
}
func main() {
ctx,cancel := context.WithCancel(context.Background())
// 启动一个协程
go req3(ctx)
// 模拟耗时操作
num := 1
for {
if num>3 {
cancel()
}
time.Sleep(time.Second)
num++
}
}
你会发现和 WithValue 相比,返回参数里面他多了一个 cancel 方法。
这个方法一但调用就会向 Context 里面传送取消信号。
再次强调,是传递取消信号,不是直接干掉那个方法。
至于我们收到取消信号后,做什么完全取决你的业务需要。
这个很好理解,就是超时后他们自动为我们调用 cancel 方法。
这样我们就免去了自己去写定时到了时间调取消。
范例代码如下:
func req4(ctx context.Context) {
select {
case <-ctx.Done():
fmt.Println("超时或者被取消")
return
}
}
func main() {
ctx,cancel := context.WithTimeout(context.Background(),time.Second*3)
go req4(ctx)
// 模拟耗时操作
num := 1
for {
if num>5 {
cancel()
}
time.Sleep(time.Second)
num++
}
}
看完代码你才猜会输出几次“超时或者被取消”?
答案是只有一次。
虽然我们在里面有两处地方调用,但是他只会打印一次,因为在传递了一次取消之后这个通道就关闭了。
如果你实在是需要知道到底是超时还是手动取消调用的,你可以收到信号时打印下 ctx.Error() 方法所输出的值。
比如这样:
func req4(ctx context.Context) {
select {
case <-ctx.Done():
fmt.Println("超时或者被取消",ctx.Err().Error())
return
}
}
当时手动取消的,会打印:
超时或者被取消 context deadline exceeded
如果是超时取消的,会打印:
超时或者被取消 context canceled
在看完这几个用处之后,是不是对 Context 的作用有了更深的了解了。
因为我们在开发中,并发的情况会经常出现,于是会开很多的 goroutine 在这么多的 goroutine 之间传递信号这些一般都会考虑使用 Context 来完成。
所以他的重要性还是很大的。
使用 Context 的程序包需要遵循如下的原则来满足接口的一致性以及便于静态分析。
在子Context被传递到的goroutine中,应该对该子Context的Done信道(channel)进行监控,一旦该信道被关闭(即上层运行环境撤销了本 goroutine 的执行),应主动终止对当前请求信息的处理,释放资源并返回。