你应该优先选择context.TODO()或context.Background()中的哪一个?

huangapple go评论81阅读模式
英文:

context.TODO() or context.Background(), which one should I prefer?

问题

我目前正在将我们的代码从全局签名包迁移到go mongo-driver,不确定应该在哪里使用context.TODO()context.Background(),这真的很令人困惑。我知道它们都返回非nil的空值,所以我应该在主函数和init()函数中使用context.Background()吗?在其他地方使用context.TODO()?有人可以帮忙解答吗?

我想检查一下应该使用context.TODO()还是context.Background()

英文:

I'm currently working on migrating our code from global sign package to go mongo-driver, not sure where should I use context.TODO() and context.Background(), it’s really confusing, I know both it returns non-nil empty, so should I use context.Background() in the main function & init() functions? And use context.TODO() in other places? Can anyone help with this?

Trying to check to see which param should I use context.TODO() or context.Background().

答案1

得分: 16

请查阅他们的文档:

context.Background():

> func Background() Context
> Background 返回一个非空的、空的 Context。它永远不会被取消,没有值,也没有截止时间。它通常由主函数、初始化和测试使用,并作为传入请求的顶级 Context。

context.TODO():

> func TODO() Context
> TODO 返回一个非空的、空的 Context。当不清楚使用哪个 Context 或者尚未可用时(因为周围的函数尚未扩展以接受 Context 参数),代码应该使用 context.TODO。

根据文档,当你需要一个 Context,但你还没有一个,并且不知道该使用什么时,使用 context.TODO()。这正是你的情况,正确地使用 context.TODO() 可以对此进行文档化。此外,静态分析工具和集成开发环境可能会支持发现这些上下文,并在稍后提醒你解决这个问题。

还要注意,如果在使用 MongoDB 驱动程序时已经有了一个 Context,那么考虑使用该 Context,或者从该 Context 派生一个新的 Context。

例如,如果你正在编写一个需要查询一些文档的 HTTP 处理程序,HTTP 请求 http.Request 已经有一个可以使用的 Context,你可以使用 Request.Context() 来访问它。当你调用其他需要 Context 的 API 函数时,这是一个主要的候选项。

为什么呢?因为当 HTTP 客户端放弃/中止请求时,请求的 Context 被取消,这意味着无论你做什么,客户端都不会收到它,所以例如,如果 HTTP 处理程序只是提供信息,你可以放弃生成它,节省一些资源。因此,如果在执行 MongoDB 查询时取消了请求的 Context(例如 Collection.Find()),这也是告诉 MongoDB 服务器在可能的情况下取消查询(因为你不需要结果)的方法。

因此,在这种情况下使用请求的 Context 可以节省 HTTP 服务器和 MongoDB 服务器上的一些 CPU 时间和内存。

另一个例子:假设你必须在 10 秒内生成 HTTP 响应(可能是平台限制),在此期间你必须执行一个 MongoDB 操作,并且你必须对结果进行后处理,这需要 4 秒。在这种情况下,如果 MongoDB 操作超过 6 秒,你将无法在 10 秒的限制内完成后处理。因此,如果 MongoDB 操作在 6 秒内未完成,你可以取消它。

解决这个问题的一种简单方法是从 6 秒超时派生一个 Context:

ctx, cancel := context.WithTimeout(r.Context(), 6 * time.Second)
defer cancel()

// ctx 在 6 秒后自动超时
curs, err := c.Find(ctx, bson.M{"some": "filter"})

在这个例子中,ctx 将在 6 秒后超时,所以如果 c.Find() 操作在那之前没有完成,传递的 ctx 将发出可以取消的信号(在这种情况下会返回一个错误)。由于 ctx 是从 r.Context() 派生的,如果请求的 Context 在此之前被取消(例如,HTTP 客户端中止),ctx 也将被取消。

现在假设你不是在编写一个 HTTP 处理程序,而是在编写其他代码(独立应用程序、后台工作程序等),在这种情况下可能没有一个 Context,但你有一个标准,即不希望等待超过 30 秒的查询。这是一个典型的例子,你可以使用 30 秒超时从 context.Background() 派生一个 Context:

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

// ctx 在 30 秒后自动超时
curs, err := c.Find(ctx, bson.M{"some": "filter"})

如果查询在 30 秒内完成,一切正常,你可以使用结果。如果不是,30 秒后上下文被取消,因此 c.Find() 也将(很可能)返回一个错误。

英文:

Check their documentation:

context.Background():

> func Background() Context
> Background returns a non-nil, empty Context. It is never canceled, has no values, and has no deadline. It is typically used by the main function, initialization, and tests, and as the top-level Context for incoming requests.

context.TODO():

> func TODO() Context
> TODO returns a non-nil, empty Context. Code should use context.TODO when it's unclear which Context to use or it is not yet available (because the surrounding function has not yet been extended to accept a Context parameter).

According to the doc, when you need a context but you don't have one (yet) and don't know what to use, use context.TODO(). This is exactly your case, using context.TODO() properly documents this. Also, static analysis tools and IDEs may give support discovering these contexts and give you warning later to address the issue.

Also note that if you do have a context when you have to use the mongo driver, then consider using that context, or deriving a new one from it.

For example, if you are writing an HTTP handler where you need to query some documents, the HTTP request http.Request already has a context which you can access using Request.Context(). This is a prime candidate to use when you call other API functions that require a context.

Why, you may ask? Because the request's context is cancelled when the HTTP client abandons / aborts the request, which means whatever you do, the client will not receive it, so for example if the HTTP handler just serves information, you may as well abort generating it, saving some resources. So if the request context is cancelled while you're executing a MongoDB query, you may as well cancel the query too, because you won't need (won't process) the result anyway. Cancelling the context passed to the query execution (Collection.Find() for example) is the way to tell the MongoDB server to cancel the query if possible (because you won't need the result).

So using the request context in this case can save you some CPU time and memory both on the HTTP server and on the MongoDB server as well.

Another example: let's say you have to produce the HTTP response within 10 seconds (may be a platform limitation), during which you have to execute a MongoDB operation, and you have to post process the results which takes 4 seconds. In this scenario if the MongoDB operation takes more than 6 seconds, you would not be able to complete the post processing to fit in the 10-second limit. So you might as well cancel the MongoDB operation if it doesn't complete in 6 seconds.

An easy way to solve this is to derive a context with 6 seconds timeout:

ctx, cancel := context.WithTimeout(r.Context(), 6 * time.Second)
defer cancel()

// ctx automatically times out after 6 seconds
curs, err := c.Find(ctx, bson.M{"some": "filter"})

In this example ctx will time out after 6 seconds, so if the c.Find() operation doesn't get finished by that, the passed ctx will signal that it can be cancelled (in which case an error will be returned). Since ctx is derived from r.Context(), if the request's context get's cancelled earlier (e.g. the HTTP client aborts), ctx will also be cancelled.

Now let's say you're not writing an HTTP handler but some other code (standalone app, background worker etc.), where you may not have a context, but you have your standards that you don't want to wait more than 30 seconds for a query. This is a prime example where you may derive a context from context.Background() using a 30-sec timeout:

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

// ctx automatically times out after 30 seconds
curs, err := c.Find(ctx, bson.M{"some": "filter"})

If the query finishes within 30 seconds, all good, you may use the results. If not, the context gets cancelled after 30 sec, and so c.Find() will also (likely) return an error.

答案2

得分: 5

context.TODO

> 当不清楚要使用哪个上下文或者上下文尚不可用(因为周围的函数尚未扩展以接受上下文参数)时,代码应该使用context.TODO

context.Background

> Background 返回一个非空的空上下文。它永远不会被取消,没有任何值,也没有截止日期。它通常由主函数、初始化和测试使用,并作为传入请求的顶级上下文。

TODO 是一个占位符,用于明确表示将来应该使用更合适的上下文来更新代码,但目前还没有可用的上下文。Background 用于当不需要这样的指示时,只需要一个空的根上下文,没有任何值或取消。它们返回的值是相同的;唯一的区别在于语义,即可以扫描代码以查找TODO上下文(或使用 linter 进行扫描),以确定需要更新/处理的位置。

英文:

context.TODO:

> Code should use context.TODO when it's unclear which Context to use or it is not yet available (because the surrounding function has not yet been extended to accept a Context parameter).

context.Background:

> Background returns a non-nil, empty Context. It is never canceled, has no values, and has no deadline. It is typically used by the main function, initialization, and tests, and as the top-level Context for incoming requests.

TODO is a placeholder to make it clear that the code should be updated with a more appropriate context in the future, but none is available yet. Background is used when such an indication isn't necessary and you simply need an empty root context with no values or cancellation. The values they return are identical; the only difference is semantic, in that one might scan code for TODO contexts (or use a linter to do so) as places that need to be updated/addressed.

huangapple
  • 本文由 发表于 2022年10月29日 01:42:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/74239074.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定