简体   繁体   English

使用 errgroup 在第一个错误时取消 goroutines

[英]Cancel goroutines on first error using errgroup

I'm trying to cancel remaining goroutines after an error being encountered in one of them or at least cancel the fetch function call in them.我试图在其中一个遇到错误后取消剩余的 goroutine,或者至少取消其中的 fetch 函数调用。

func fetch(n int, fail bool) (string, error) {
  time.Sleep(time.Duration(n) * time.Second)
  if fail {
    return "", errors.New("an error") //fmt.Errorf("an error")
  } else {
    return "Hello", nil
  }
}

func getData(ctx context.Context, ch chan string, n int, fail bool) error {
  fmt.Println("fetch data" + strconv.Itoa(n))

  select {
  case <-ctx.Done():
    return ctx.Err()
  default:
    if response, err := fetch(n, fail); err != nil {
        fmt.Println("error encountered at ", strconv.Itoa(n))
        return err
    } else {
        ch <- response
        fmt.Println("fetched data" + strconv.Itoa(n))
        return nil
    }
  }
}

func main() {
  res, err := func() (string, error) {
    ctx := context.Background()
    g, ctx := errgroup.WithContext(ctx)

    ch1 := make(chan string, 1)
    ch2 := make(chan string, 1)
    ch3 := make(chan string, 1)

    g.Go(func() error {
        defer close(ch1)
        return getData(ctx, ch1, 1, true)
    })

    g.Go(func() error {
        defer close(ch2)
        return getData(ctx, ch2, 2, false)
    })

    g.Go(func() error {
        defer close(ch3)
        return getData(ctx, ch3, 3, false)
    })

    result := <-ch1 + <-ch2 + <-ch3
    return result, g.Wait()
  }()

  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println(res)
  }
}

The output should look like:输出应如下所示:

fetch data1
fetch data2
fetch data3
error encountered at  1
an error

but actually is:但实际上是:

fetch data3
fetch data1
fetch data2
error encountered at  1
fetched data2
fetched data3
an error

What I understand the errgroup should be doing is closing the context when an error is found, and then <-ctx.Done() should return ctx.Err(), but instead It's like the context is never done and the fetch function keeps being called.我理解 errgroup 应该做的是在发现错误时关闭上下文,然后 <-ctx.Done() 应该返回 ctx.Err(),但它就像上下文从未完成并且 fetch 函数一直在执行叫。

I think the problem is in the select statement.我认为问题出在 select 语句中。 Any idea of what i'm doing wrong or missing?知道我做错了什么或错过了什么吗? Link to playground链接到游乐场

Pass the context to the fetch function and exit on cancel:将上下文传递给 fetch 函数并在取消时退出:

func fetch(ctx context.Context, n int, fail bool) (string, error) {  
    select {
    case <-time.After(time.Duration(n) * time.Second):
        if fail {
            return "", errors.New("an error")
        } else {
            return "Hello", nil
        }
    case <-ctx.Done():
        fmt.Println("canceled")
        return "CXL", ctx.Err()
    }
}

There's no need for the channel stuff and GetData.不需要通道内容和 GetData。 Just assign results from fetch to variables.只需将 fetch 的结果分配给变量。

ctx := context.Background()
g, ctx := errgroup.WithContext(ctx)

var r1, r2, r3 string

g.Go(func() error {
    var err error
    r1, err = fetch(ctx, 1, true)
    return err
})

g.Go(func() error {
    var err error
    r2, err = fetch(ctx, 2, false)
    return err
})

g.Go(func() error {
    var err error
    r3, err = fetch(ctx, 3, false)
    return err
})

err := g.Wait()
fmt.Printf("err: %v, 1: %s; 2: %s; 3: %s\n", err, r1, r2, r3)

Run it on the playground .在操场上运行它

The context handling in getData has no effect, once the goroutines are up and running.一旦 goroutine 启动并运行, getDatacontext处理就不起作用。

The context handling should be moved into any function that potentially blocks, and ensure any blocking operations are bound to the context:上下文处理应该移到任何可能阻塞的函数中,并确保任何阻塞操作都绑定到上下文:

func fetch(ctx context.Context, n int, fail bool) (string, error) {

    // time.Sleep(time.Duration(n) * time.Second) 

    // sleep blocks - but cannot be context canceled...

    // ... use a time.Ticker channel-based read instead:

    select {
    case <-time.After(time.Duration(n) * time.Second):
        // sleep finished uninterrupted

    case <-ctx.Done():
        return "", ctx.Err()
    }

    if fail {
        return "", errors.New("an error")
    } else {
        return "Hello", nil
    }
}

https://play.golang.org/p/kGjmmAlGsG6 https://play.golang.org/p/kGjmmAlGsG6

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM