猿问

如果上下文被取消,则终止函数执行

我有这个最初不支持上下文的当前函数。


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

}

我已经更新了它的方法调用,现在将 acontext.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但是这个选项似乎也不正确,因为我需要在每个函数中实现取消。将这个原始函数重构为上下文感知并在上下文被取消后立即终止的正确方法是什么?


qq_遁去的一_1
浏览 104回答 2
2回答

斯蒂芬大帝

函数调用和 goroutines 不能被调用者终止,函数和 goroutines 必须支持取消,通常通过context.Context值或done通道。在任何一种情况下,函数都负责检查/监视上下文,如果请求取消(当上下文的完成通道关闭时),则提前返回。没有更简单/自动的方法。如果任务在循环中执行代码,一个方便的解决方案是在每次迭代中检查完成的通道,如果它关闭则返回。如果任务是一个“整体”,则实现者负责使用/插入“检查点”,如果请求取消,则可以合理地提前中止任务。检查 done 通道是否关闭的一种简单方法是使用 non-blocking select,例如:select {case <-ctx.Done():&nbsp; &nbsp; // Abort / return early&nbsp; &nbsp; returndefault:}当任务使用其他通道操作时必须小心,因为它们可能会以不确定的方式阻塞。这些选择也应该包括ctx.Done()频道:select {case v := <- someChannel:&nbsp; &nbsp; // Do something with vcase <-ctx.Done():&nbsp; &nbsp; // Abort / return early&nbsp; &nbsp; return}还要小心,因为如果上面的 receive from someChannelnever 阻塞,则不能保证正确处理取消,因为如果多个通信可以在 a 中进行select,则随机选择一个(并且不能保证<-ctx.Done()永远选择)。在这种情况下,您可以结合上述 2:首先对取消进行非阻塞检查,然后将 aselect与您的通道操作和取消监控一起使用。

温温酱

当我们谈到取消时,我们谈到了一个长期运行的函数或一个重复多次的块,例如http.Serve()至于您的情况,假设saveChunk运行将花费几秒钟,并且您想在保存时取消。所以我们可以把块分成几块,在每一块之后一个接一个地保存。for i:=0;i<n;i++{&nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; case err := <- s.saveChunk(chunk[i]):&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;if err != nil {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fmt.Errorf("failed saving chunk %w", err)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; case <-ctx.Done():&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; }}
随时随地看视频慕课网APP

相关分类

Go
我要回答