go - Go ctx.Done() 永远不会在 select 语句中触发
问题描述
我正在开发的模块有以下代码,但我不确定为什么在provider.Shutdown()
调用时从未调用过该函数.Stop()
主要过程确实停止了,但我很困惑为什么这不起作用?
package pluto
import (
"context"
"fmt"
"log"
"sync"
)
type Client struct {
name string
providers []Provider
cancelCtxFunc context.CancelFunc
}
func NewClient(name string) *Client {
return &Client{name: name}
}
func (c *Client) Start(blocking bool) {
log.Println(fmt.Sprintf("Starting the %s service", c.name))
ctx, cancel := context.WithCancel(context.Background())
c.cancelCtxFunc = cancel // assign for later use
var wg sync.WaitGroup
for _, p := range c.providers {
wg.Add(1)
provider := p
go func() {
provider.Setup()
select {
case <-ctx.Done():
// THIS IS NEVER CALLED?!??!
provider.Shutdown()
return
default:
provider.Run(ctx)
}
}()
}
if blocking {
wg.Wait()
}
}
func (c *Client) RegisterProvider(p Provider) {
c.providers = append(c.providers, p)
}
func (c *Client) Stop() {
log.Println("Attempting to stop service")
c.cancelCtxFunc()
}
客户端代码
package main
import (
"pluto/pkgs/pluto"
"time"
)
func main() {
client := pluto.NewClient("test-client")
testProvider := pluto.NewTestProvider()
client.RegisterProvider(testProvider)
client.Start(false)
time.Sleep(time.Second * 3)
client.Stop()
}
解决方案
case
因为它在上下文被取消之前已经选择了另一个。这是您的代码,注释:
// Start a new goroutine
go func() {
provider.Setup()
// Select the first available case
select {
// Is the context cancelled right now?
case <-ctx.Done():
// THIS IS NEVER CALLED?!??!
provider.Shutdown()
return
// No? Then call provider.Run()
default:
provider.Run(ctx)
// Run returned, nothing more to do, we're not in a loop, so our goroutine returns
}
}()
一旦provider.Run
被调用,取消上下文不会在显示的代码中做任何事情。provider.Run
虽然也得到了上下文,所以它可以自由地处理它认为合适的取消。如果您希望您的例程也看到取消,您可以将其包装在一个循环中:
go func() {
provider.Setup()
for {
select {
case <-ctx.Done():
// THIS IS NEVER CALLED?!??!
provider.Shutdown()
return
default:
provider.Run(ctx)
}
}
}()
这样一来,一旦provider.Run
返回,它会select
再次经过,如果上下文已被取消,则将调用该案例。但是,如果上下文没有被取消,它会provider.Run
再次调用,这可能是也可能不是你想要的。
编辑:
更典型的是,您会遇到几种情况之一,具体取决于问题的方式provider.Run
和provider.Shutdown
工作方式,这在问题中并未明确,因此您可以选择:
Shutdown
必须在上下文被取消时调用,并且Run
只能调用一次:
go func() {
provider.Setup()
go provider.Run(ctx)
go func() {
<- ctx.Done()
provider.Shutdown()
}()
}
OrRun
已经接收到上下文,已经做了与取消上下文时相同的事情Shutdown
,因此在取消上下文时调用Shutdown
是完全没有必要的:
go provider.Run(ctx)
推荐阅读
- iis - .NET Core 3.0 GA 与 .NET Core 3.0 Preview 8 并行
- json - 如何为接收到的 JSON 格式定义没有 POJOS 的数据模型?
- laravel - 如何从两个单独的表中调用变量
- javascript - 如何设置条件函数以响应一个组件而不是更大的组件
- amazon-web-services - 我正在尝试使用 AWS EB CLI (elastic beanstalk) 部署 python 应用程序
- java - CloseableHttpClient 和 CloseableHttpResponse 是否都需要显式关闭
- r - 如何启动多个 R 进程在同一个端口上侦听?
- oracle - Oracle 触发器有条件地审计插入
- angular - 使用 ngx-bootstrap 将服务的特定实例传递给模式的正确方法
- typescript - 创建仅使用字符串键扩展接口的 TypeScript 泛型类型