上下文没有取消传播

Nes*_*kil 4 asynchronous go goroutine cancellation

如何创建包含原始文件中存储的所有值的Go上下文的副本(如果愿意,则为克隆),但在原始文件中不会被取消?

它似乎对我来说是一个有效的用例.假设我有一个http请求,并且在将响应返回给客户端后取消其上下文,并且我需要在单独的goroutine中在此请求的末尾运行异步任务,该任务很可能比父上下文更长.

func Handler(ctx context.Context) (interface{}, error) {
        result := doStuff(ctx)
        newContext := howDoICloneYou(ctx)
        go func() {
                doSomethingElse(newContext)
        }()
        return result
}
Run Code Online (Sandbox Code Playgroud)

任何人都可以建议如何做到这一点?

当然,我可以跟踪可能放入上下文的所有值,创建一个新的后台ctx,然后只是迭代每个可能的值并复制......但这似乎很乏味,很难在大型代码库中进行管理.

CAF*_*FxX 9

从 go 1.21 开始,可以通过以下方式在标准库中直接使用此功能context.WithoutCancel

func WithoutCancel(parent Context) Context
Run Code Online (Sandbox Code Playgroud)

WithoutCancel返回父级取消时未取消的父级副本。返回的上下文返回 noDeadlineErr,其Done通道为nil。调用Cause返回的上下文会返回nil


Pet*_*ter 6

由于context.Context是一个接口,因此您可以简单地创建自己的实现,该实现永远不会被取消:

import (
    "context"
    "time"
)

type noCancel struct {
    ctx context.Context
}

func (c noCancel) Deadline() (time.Time, bool)       { return time.Time{}, false }
func (c noCancel) Done() <-chan struct{}             { return nil }
func (c noCancel) Err() error                        { return nil }
func (c noCancel) Value(key interface{}) interface{} { return c.ctx.Value(key) }

// WithoutCancel returns a context that is never canceled.
func WithoutCancel(ctx context.Context) context.Context {
    return noCancel{ctx: ctx}
}
Run Code Online (Sandbox Code Playgroud)

  • 根据定义,只要此上下文位于父级周围,就不是垃圾,因为仍然存在对它的引用。访问垃圾收集值是一个矛盾的说法。 (7认同)
  • 如果您在该上下文中传递值,则会遇到一个大问题,因为如果父上下文已被取消,则子上下文可能不会取消该操作,但会尝试从父上下文中读取值,即可能已经被垃圾收集了。 (2认同)

Vol*_*ker 5

任何人都可以建议如何做到这一点?

是.不要这样做.

如果需要不同的上下文,例如,对于异步后台任务,则创建上下文.您的传入上下文和后台任务之一是无关的,因此您不能尝试重用传入的上下文.

如果不相关的新上下文需要原始数据:复制您需要的内容并添加新内容.

  • 异步 http 处理程序的情况怎么样?返回 202 并从处理程序返回后,请求的上下文将很快被取消。您可能需要异步 Goroutine 中的上下文来完成当前的工作。如果服务器具有依赖于向上下文写入和读取请求范围数据的中间件(例如日志记录),则可能需要来自请求上下文的值。处理程序本身当然不应该知道所有这些值(中间件的内部),以便它可以将它们复制到新的上下文。 (3认同)
  • 并非所有上下文都是无关的。我可能想跟踪数据库调用,但如果父上下文被取消,则不会取消它。 (3认同)