我有这个当前功能,最初是不了解上下文的。
func (s *Service) ChunkUpload(r *multipart.Reader) error {
chunk, err := s.parseChunk(r)
if err != nil {
return fmt.Errorf("failed parsing chunk %w", err)
}
if err := os.MkdirAll(chunk.UploadDir, 02750); err != nil {
return err
}
if err := s.saveChunk(chunk); err != nil {
return fmt.Errorf("failed saving chunk %w", err)
}
return nil
}
我已经更新了它的方法调用,现在将
context.Context
作为其第一个参数。我的主要目标是在取消上下文后立即终止并返回函数。我最初的实现是这样。
func (s *Service) ChunkUpload(ctx context.Context, r *multipart.Reader) error {
errCh := make(chan error)
go func() {
chunk, err := s.parseChunk(r)
if err != nil {
errCh <- fmt.Errorf("failed parsing chunk %w", err)
return
}
if err := os.MkdirAll(chunk.UploadDir, 02750); err != nil {
errCh <- err
return
}
if err := s.saveChunk(chunk); err != nil {
errCh <- fmt.Errorf("failed saving chunk %w", err)
return
}
}()
select {
case err := <-errCh:
return err
case <-ctx.Done():
return ctx.Err()
}
}
但是,当我想到执行代码时,我意识到这并没有实现我的目标。由于所有函数的逻辑都在单独的go例程中,即使上下文已取消,并且我提早返回了
ChunkUpload
,go例程中的代码仍将继续执行,因此与原始代码没有任何区别。尽管下一个是可以的,只是将上下文传递给所有内部函数(例如
s.parseChunk
和s.saveChunk
),但是此选项似乎也不正确,因为我需要在每个函数中实现取消。重构此原始功能使其具有上下文意识并在取消上下文后立即终止的正确方法是什么?
最佳答案
无法从调用方终止函数调用和goroutine,函数和goroutine必须支持取消,通常是通过context.Context
值或done
channel 进行。
在这两种情况下,函数均负责检查/监视上下文,并且如果请求取消(当关闭上下文的完成 channel 时),请提早返回。没有一种简单/自动的方法。
如果任务在循环中执行代码,一种简便的解决方案是在每次迭代中检查已完成的 channel ,并在 channel 关闭时返回。如果任务是一个“整体”,则实现者负责使用/插入“检查点”,如果请求取消,则可以在该检查点尽早中止该任务。
检查完成的 channel 是否关闭的一种简单方法是使用非阻塞的select
,例如:
select {
case <-ctx.Done():
// Abort / return early
return
default:
}
任务使用其他 channel 操作时,必须小心,因为它们可能以不确定的方式阻塞。这些选择也应包括
ctx.Done()
channel :select {
case v := <- someChannel:
// Do something with v
case <-ctx.Done():
// Abort / return early
return
}
还要小心,因为如果上面从
someChannel
接收的消息从未阻塞,则不能保证取消处理正确,因为如果可以在select
中进行多次通信,则会随机选择一个(并且不能保证曾经选择过<-ctx.Done()
)。在这种情况下,您可以结合上述2种方法:首先进行非阻塞检查以进行取消,然后将select
与 channel 操作和一起使用以进行取消监视。
关于go - 如果取消上下文,则终止函数执行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62002729/