context.WithoutCancel

143 阅读2分钟

在使用golang写业务的时候,部分场景需要异步执行,而且需要带上完整的context值,这个时候就需要用到WithoutCancel。

示例:

package main

import (
	"context"
	"log"
	"net/http"
	"time"
)

func init() {
	log.SetFlags(log.Ltime | log.Lmicroseconds | log.Lshortfile)
}

func main() {
	defer func() {
		time.Sleep(10 * time.Second)
		log.Println("退出")
	}()
	log.Println("开始")
	ctx := context.TODO()
	ctx = context.WithValue(ctx, "abc", "123")
	ctx, _ = context.WithTimeout(ctx, time.Second*3)
	syncA(ctx)
	log.Println("结束")
}

func syncA(ctx context.Context) {
	deadline, ok := ctx.Deadline()
	if ok {
		log.Printf("syncA 过期时间:%+v\n", deadline)
	} else {
		log.Println("syncA 没有过期时间", deadline)
	}
	time.Sleep(time.Second)
	b := syncB(ctx)
	log.Printf("syncA b:%s\n", b)
	return
}

func syncB(ctx context.Context) string {
	deadline, ok := ctx.Deadline()
	if ok {
		log.Printf("syncB 过期时间:%+v\n", deadline)
	} else {
		log.Println("syncB 没有过期时间", deadline)
	}
	time.Sleep(time.Second)
	go asyncC(ctx)
	return time.Now().Format("150405.000")
}

func asyncC(ctx context.Context) {
	time.Sleep(5 * time.Second)
	ctx = context.WithoutCancel(ctx)
	ctx, _ = context.WithTimeout(ctx, time.Second*5)
	deadline, ok := ctx.Deadline()
	if ok {
		log.Printf("syncC 过期时间:%+v\n", deadline)
	} else {
		log.Println("syncC 没有过期时间", deadline)
	}
	log.Printf("abc的值:%v\n", ctx.Value("abc"))
	httpGet(ctx)
}

func httpGet(ctx context.Context) {
	req, err := http.NewRequestWithContext(ctx, "GET", "http://www.baidu.com", nil)
	if err != nil {
		log.Fatal(err)
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	log.Printf("syncC Response Status:%v\n", resp.Status)
}
09:24:51.581463 context.go:19: 开始
09:24:51.581837 context.go:30: syncA 过期时间:2025-03-21 09:24:54.581803 +0800 CST m=+3.001079543
09:24:52.582950 context.go:43: syncB 过期时间:2025-03-21 09:24:54.581803 +0800 CST m=+3.001079543
09:24:53.584176 context.go:36: syncA b:092453.584
09:24:53.584289 context.go:24: 结束
09:24:58.585230 context.go:58: syncC 过期时间:2025-03-21 09:25:03.585226 +0800 CST m=+12.004548001
09:24:58.585368 context.go:62: abc的值:123
09:24:58.618136 context.go:78: syncC Response Status:200 OK
09:25:03.584522 context.go:17: 退出

如果不使用WithoutCancel,就能看到错误:

10:55:48.538943 context.go:19: 开始
10:55:48.539247 context.go:30: syncA 过期时间:2025-03-24 10:55:51.539221 +0800 CST m=+3.000890417
10:55:49.540321 context.go:43: syncB 过期时间:2025-03-24 10:55:51.539221 +0800 CST m=+3.000890417
10:55:50.541462 context.go:36: syncA b:105550.541
10:55:50.541506 context.go:24: 结束
10:55:55.541914 context.go:58: syncC 过期时间:2025-03-24 10:55:51.539221 +0800 CST m=+3.000890417
10:55:55.542013 context.go:62: abc的值:123
10:55:55.542283 context.go:74: Get "http://www.baidu.com": context deadline exceeded