Go1.20 新特性:context支持自定义取消原因
2023-1-9 08:54:49 Author: Go语言中文网(查看原文) 阅读量:13 收藏

问题

熟悉 Go 语言的同学都知道,context 包只对外提供了两种取消原因 context.DeadlineExceeded 和 context.Canceled,不支持自定义原因,就像下面这样:

func main() {
    // Pass a context with a timeout to tell a blocking function that it
    // should abandon its work after the timeout elapses.
    timeoutDuration := 3 * time.Second
    ctx, cancel := context.WithTimeout(context.Background(), timeoutDuration)
    defer cancel()

    // block until context is timed out
    <-ctx.Done()

    switch ctx.Err() {
        case context.DeadlineExceeded:
            fmt.Println("context timeout exceeded")
        case context.Canceled:
            fmt.Println("context cancelled by force")
    }
   
   // output:
   // context timeout exceeded
}

上面的两种错误已经能够满足大部分场景,但是如果有时候我想知道更多关于 context 取消的原因就只能额外自定义 error,比如遇到某类取消错误时是否需要重试等。

另外,如果是显示地调用 context.CancelFunc() 函数(上面代码的 cancel  变量)取消,现在是没有办法表明这是否是由于错误造成的。

介于此,之前社区就有人提案:如果是显示取消 context,允许自定义取消原因。

这不它就来了,Go1.20 目前已经支持这一特性。

自定义取消原因

Go1.20 的 context 包提供了 context.WithCancelCause() 支持自定义取消原因,并且提供了提取取消原因的 api:

package main

import (
    "context"
    "errors"
    "fmt"
)

var ErrTemporarilyUnavailable = fmt.Errorf("service temporarily unavailable")

func main() {
    ctx, cancel := context.WithCancelCause(context.Background())

    // operation failed, let's notify the caller by cancelling the context
    cancel(ErrTemporarilyUnavailable)

    switch ctx.Err() {
    case context.Canceled:
        fmt.Println("context cancelled by force")
    }

    // get the cause of cancellation, in this case the ErrTemporarilyUnavailable error
    err := context.Cause(ctx)

    if errors.Is(err, ErrTemporarilyUnavailable) {
        fmt.Printf("cancallation reason: %s", err)
    }
    
    // cancallation reason: service temporarily unavailable
}

上面的代码,在取消的时候传入了自定义错误 “ErrTemporarilyUnavailable”,并且使用 context.Cause() 提取错误原因。

有了这一特性,以后就可以基于取消原因,做更近一步的逻辑操作了,比如是否需要重试等。

更进一步

跟着 context.WithCancelCause() 之后,目前有最新的提案,支持 WithDeadlineCause 和 WithTimeoutCause,目前该提案已经被官方接受,正在开发。

我们先来尝鲜,看下这两个分别怎么用?

context.WithTimeoutCause()


var ErrFailure = fmt.Errorf("request took too long")

func main() {
    timeout := time.Duration(2 * time.Second)
    ctx, _ := context.WithTimeoutCause(context.Background(), timeout, ErrFailure)

    // wait for the context to timeout
    <-ctx.Done()

    switch ctx.Err() {
    case context.DeadlineExceeded:
        fmt.Printf("operation could not complete: %s", context.Cause(ctx))
    }
        
    // operation could not complete: request took too long 
}

context.WithDeadlineCause()

var ErrFailure = fmt.Errorf("request took too long")

func main() {
    timeout := time.Now().Add(time.Duration(2 * time.Second))
    ctx, _ := context.WithDeadlineCause(context.Background(), timeout, ErrFailure)

    // wait for the context to timeout
    <-ctx.Done()

    switch ctx.Err() {
    case context.DeadlineExceeded:
        fmt.Printf("operation could not complete: %s", context.Cause(ctx))
    }
        
    // operation could not complete: request took too long 
}

这些新特性有没有给你编程带来便利呢?欢迎留言“开喷”!


推荐阅读

福利
我为大家整理了一份从入门到进阶的Go学习资料礼包,包含学习建议:入门看什么,进阶看什么。关注公众号 「polarisxu」,回复 ebook 获取;还可以回复「进群」,和数万 Gopher 交流学习。


文章来源: http://mp.weixin.qq.com/s?__biz=MzAxMTA4Njc0OQ==&mid=2651453958&idx=1&sn=a063f923ee4ebb53da951e18faee9628&chksm=80bb24f4b7ccade21fb05826f39e7e2d16c83753168f25080d4485e6d4fed085440d55c82237#rd
如有侵权请联系:admin#unsafe.sh