跳转到主要内容
模块 7 · 第 5 课(共 5)课程中的35/50~14 min
模块课程(5/5)

`context.Context`:取消和截止日期

context 包是 传播的事实上的标准 调用链上的取消、截止日期和范围限制值, 跨多个 goroutine。所有标准“网络”包 (net/http, database/sql, os/exec) 接受 context.Context 作为 他们的第一个参数。

界面

Go
type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key any) any
}
  • Done():取消上下文时关闭的通道。
  • Err():取消原因(context.Canceled 或 context.DeadlineExceeded)。
  • Deadline():可选的截止日期。
  • Value():查找范围限制值(谨慎使用)。

四个基本功能

Go
ctx := context.Background()              // radice: usalo in main, init, test
ctx := context.TODO()                    // "non so ancora": stub esplicito

ctx, cancel := context.WithCancel(parent)               // cancel manuale
ctx, cancel := context.WithTimeout(parent, d)           // scadenza relativa
ctx, cancel := context.WithDeadline(parent, t)          // scadenza assoluta

ctx := context.WithValue(parent, key, value)            // valore scope-bound

所有派生形式都返回 cancel 函数:始终调用它 (理想情况下使用 defer)释放内部资源,即使 上下文已经过期。

超时模式

Go
func fetchUser(id string) (*User, error) {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    return queryUser(ctx, id)   // queryUser deve rispettare ctx
}

如果 queryUser 花费超过 2 秒,则 ctx.Done() 将关闭并 该函数必须中止操作。

尊重上下文:选择 Done()

Go
func slowWork(ctx context.Context) error {
    for {
        select {
        case <-ctx.Done():
            return ctx.Err()      // canceled o deadline exceeded
        case <-time.After(100 * time.Millisecond):
            // un tick di lavoro
        }
    }
}

规则:任何地方你的函数可能会阻塞很长时间, 必须处理 ctx.Done()。否则,上下文将“通电” 什么也没有。

第一参数约定

Go
func Fetch(ctx context.Context, url string) ([]byte, error) { ... }
//        ^^^^^^^^^^^^^^^^^^^
// SEMPRE primo, mai dentro a una struct, mai opzionale
  • ctx 始终作为第一个参数,名为 ctx。
  • 永远不要将 Context 作为结构体的字段(非常罕见的例外)。
  • 从不传递 nil:使用 context.TODO() 作为存根。

context.WithValue:谨慎

Go
type userKey struct{}

ctx := context.WithValue(ctx, userKey{}, currentUser)
// più sotto:
if u, ok := ctx.Value(userKey{}).(*User); ok { ... }

指南:

  • 仅适用于 请求范围 数据(请求 ID、跟踪 ID、用户身份)。
  • 绝不用于函数参数:正常传递它们。
  • 自定义非导出类型的密钥以避免冲突。

沿链传播

Go
func handleHTTP(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()   // context legato al ciclo di vita della richiesta
    user, err := fetchUser(ctx, r.URL.Query().Get("id"))
    if err != nil { ... }
    // ...
}

net/http 当客户端自动取消 r.Context() 断开连接或处理程序返回时。通过向所有人传播 下游调用,整个操作树被取消 级联:断开连接后不会浪费工作。

尝试一下

锻炼#go.m7.l5.e1
尝试:0加载中...

创建一个具有从 context.Background() 派生的 1 秒超时的上下文,并记住使用 defer 调用 cancel。

正在加载编辑器...
显示提示

`context.WithTimeout(parent, d)` 返回 `(ctx, cancel)`;总是打电话取消。

3 次尝试后可用的解决方案

锻炼#go.m7.l5.e2
尝试:0加载中...

在选择中使用 <-ctx.Done() 等待上下文被取消。

正在加载编辑器...
显示提示

`ctx.Done()` 是一个在上下文取消时关闭的通道。

3 次尝试后可用的解决方案

Quiz#go.m7.l5.e3
准备好

按照惯例,ctx 参数在函数签名中位于哪里?

Go
func Fetch(???, url string) ([]byte, error)
答案选项

回顾

  • context.Context 传播取消、截止日期和范围限制值。
  • Background()/TODO() 作为根; WithCancel/WithTimeout/WithDeadline 来派生。
  • 始终使用 defer cancel() 来释放资源。
  • “长”函数必须在 ctx.Done() 上 select 或将 ctx 传递到标准包。
  • 约定:ctx 作为第一个参数,切勿在结构内。
  • WithValue 仅适用于请求范围的数据(请求 ID、跟踪 ID)。
  • 在 net/http 中,r.Context() 在客户端断开连接时被取消。