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

标签 go channel cancellation

我有这个当前功能,最初是不了解上下文的。

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.parseChunks.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/

相关文章:

asynchronous - F# 中的协作取消与取消延续

c++ - 用 C++ 编写取消操作背后的概念是什么?

go - fmt.Printf ("%f\n",x) 将 Float64 舍入为 Float32,但 fmt.Println(x) 不会

go - Go 运行时怎么可能用 Go 编写?

Golang channel 不关闭

c - 疯狂的宏hack,用于处理线程取消和清理处理程序的问题

go - 强制重新下载/完全清洁环境

go - 由于没有 HTTP 重定向,执行模板提交问题?

android - channel 不可恢复地损坏,将被处理掉

youtube - 如何从youtube api获取 channel 相关数据