One of the drawbacks of this format I’ve chosen—reading through a document, and providing commentaty, in order—is that sometimes advanced topics come up before the foundational ones they rely on.
Today is one such case.
Typically, context cancelation is used to terminate an action in progress. But it can also be used to begin an action. One way to accomplish this is to wait for a cancelation signal in a goroutine:
go func() {
<- ctx.Done()
doSomething()
}()
But since Go 1.21, there’s a more flexible option:
Functions
func AfterFunc
func AfterFunc(ctx Context, f func()) (stop func() bool)
AfterFunc arranges to call f in its own goroutine after ctx is canceled. If ctx is already canceled, AfterFunc calls f immediately in its own goroutine.
Multiple calls to AfterFunc on a context operate independently; one does not replace another.
This means we could rewrite our earlier code as:
context.AfterFunc(ctx, func() {
doSomething()
})
But with additional capabilities…
Calling the returned stop function stops the association of ctx with f. It returns true if the call stopped f from being run. If stop returns false, either the context is canceled and f has been started in its own goroutine; or f was already stopped. The stop function does not wait for f to complete before returning. If the caller needs to know whether f is completed, it must coordinate with f explicitly.
stop := context.AfterFunc(ctx func() {
doSomething()
})
/* ... */
stop() // Actually, I decided not to doSomething() when ctx cancels
If ctx has a “AfterFunc(func()) func() bool” method, AfterFunc will use it to schedule the call.
And finally, if you have a custom context.Context
implementation (since it’s an interface, this is possible) that implements a method AfterFunc(func()) func() bool
, then that method is used to schedule the function passed to AfterFunc
, rather than the default behavior.
The GoDoc provides a few examples, which I’m going to skip over, since they’re rather lengthy.