正确使用上下文控制业务流

举报
码乐 发表于 2026/01/27 12:04:39 2026/01/27
【摘要】 1 简介本文简介context 的正确用法,循序渐进 + 可运行的例子给你讲清楚:如何用 ctx 管理任务是否完成 / 是否取消。 2 理清概念ctx 只做一件事:广播“该停了”这个信号任务是否完成 → 由任务自己决定ctx 只负责:取消 / 超时 / 上游结束 3 场景 1:最基础 —— 用 ctx 控制任务退出任务函数 func worker(ctx context.Contex...

1 简介

本文简介context 的正确用法,循序渐进 + 可运行的例子给你讲清楚:如何用 ctx 管理任务是否完成 / 是否取消。

2 理清概念

ctx 只做一件事:广播“该停了”这个信号

任务是否完成 → 由任务自己决定

ctx 只负责:取消 / 超时 / 上游结束

3 场景 1:最基础 —— 用 ctx 控制任务退出

任务函数

    func worker(ctx context.Context) error {
        for {
            select {
            case <-ctx.Done():
                // 上游取消 / 超时
                return ctx.Err()
            default:
                fmt.Println("working...")
                time.Sleep(1 * time.Second)
            }
        }
    }

调用方

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

    go func() {
        time.Sleep(3 * time.Second)
        cancel() // 3 秒后取消任务
    }()

    err := worker(ctx)
    fmt.Println("worker exit:", err)
  • 说明

worker 监听 ctx.Done()

cancel() 一调用

所有监听 ctx 的 goroutine 同时收到退出信号

3 场景 2:判断“任务是否正常完成 vs 被取消”

正确模式:任务返回结果

    func job(ctx context.Context) error {
        for i := 0; i < 5; i++ {
            select {
            case <-ctx.Done():
                return ctx.Err()
            default:
                time.Sleep(1 * time.Second)
            }
        }
        return nil // 正常完成
    }

调用

      ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
      defer cancel()

      err := job(ctx)
      if err == nil {
          fmt.Println("任务正常完成")
      } else {
          fmt.Println("任务被中断:", err)
      }

结论

  返回值						含义
  nil						任 务完成
  context.Canceled				被取消
  context.DeadlineExceeded			超时

4 最常见场景 :ctx + goroutine + select

      func runTask(ctx context.Context) {
          ticker := time.NewTicker(1 * time.Second)
          defer ticker.Stop()

          for {
              select {
              case <-ctx.Done():
                  fmt.Println("task stop:", ctx.Err())
                  return
              case <-ticker.C:
                  fmt.Println("task running")
              }
          }
      }

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

      time.Sleep(5 * time.Second)
      cancel()

5 场景 4:任务完成后主动结束 ctx

关键:cancel 是由创建 ctx 的地方持有的

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

      go func() {
          for i := 0; i < 10; i++ {
              time.Sleep(1 * time.Second)
          }
          fmt.Println("task finished")
          cancel() // 通知所有监听者
      }()

监听方:

      <-ctx.Done()
      fmt.Println("ctx done:", ctx.Err())

这就是 “任务完成 → 通知外部” 的标准写法。

6 工程里最推荐场景 :ctx + channel

ctx 管生死,channel 管结果

    type Result struct {
        Count int
    }

    func job(ctx context.Context, resultCh chan<- Result) {
        defer close(resultCh)

        count := 0
        for {
            select {
            case <-ctx.Done():
                return
            default:
                time.Sleep(500 * time.Millisecond)
                count++
                if count == 10 {
                    resultCh <- Result{Count: count}
                    return
                }
            }
        }
    }

    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    resultCh := make(chan Result)

    go job(ctx, resultCh)

    select {
    case res := <-resultCh:
        fmt.Println("任务完成:", res.Count)
    case <-ctx.Done():
        fmt.Println("任务失败:", ctx.Err())
    }

反例(一定不要这样)
ctx.Done() <- struct{}{} // ❌ 错

ctx = context.WithValue(ctx, “done”, true) // ❌ 滥用

time.Sleep(10 * time.Second) // ❌ 不可取消

最重要的工程口诀(建议背下来)

√ ctx:控制生命周期
√ channel:传递结果 / 状态
❌ ctx:不是消息队列

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。