如何知道上下文是否已被取消?

huangapple go评论71阅读模式
英文:

How to know if the context has been cancelled?

问题

如何判断上下文是否被取消?

在下面的示例代码中:

  • 有两个任务。
  • 如果其中任何一个任务先完成,我想通过上下文取消在其他任务中得知。
  • 作为示例,task2将始终先完成(在task1之前)。
package main

import (
	"context"
	"fmt"
	"time"
)

func task2(ctx context.Context, ch chan int) {
	for i := 0; i <= 10; i++ {
		if ctx.Err() != nil {
			// 如果task1先完成,
			// 我想打印这个错误。我该如何到达这个代码块?
			fmt.Println("Cancelled 2", ctx.Err())
		}

		fmt.Println("Task2 ===== ", i)
		time.Sleep(1 * time.Second)
		if i == 2 {
			ch <- 2
		}
	}
}

func task1(ctx context.Context, ch chan int) {
	for i := 0; i <= 10; i++ {
		if ctx.Err() != nil {
			// 如果task2先完成,
			// 我想打印这个错误。我该如何到达这个代码块?
			fmt.Println("Cancelled 1", ctx.Err())
		}

		fmt.Println("Task1 ----- ", i)
		time.Sleep(1 * time.Second)
		if i == 5 {
			ch <- 1
		}
	}
}

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	ch := make(chan int)

	go task1(ctx, ch)
	go task2(ctx, ch)
    
	d := <-ch
	cancel() // 如果task1或task2中的任何一个完成,调用cancel()函数

	fmt.Println("首先完成的任务:", d)
}
英文:

How to know if a context has been cancelled?

In a following sample code,

  • There are 2 tasks.
  • If any of these tasks completes first, I want to know in other tasks through context cancel.
  • As part of example, task2 will always finish first (before task1).
package main

import (
	&quot;context&quot;
	&quot;fmt&quot;
	&quot;time&quot;
)

func task2(ctx context.Context, ch chan int) {
	for i := 0; i &lt;= 10; i++ {
		if ctx.Err() != nil {
			// In case task1 completes first,
			// I want to print this error. How do I reach this block?
			fmt.Println(&quot;Cancelled 2&quot;, ctx.Err())
		}

		fmt.Println(&quot;Task2 ===== &quot;, i)
		time.Sleep(1 * time.Second)
		if i == 2 {
			ch &lt;- 2
		}
	}
}

func task1(ctx context.Context, ch chan int) {
	for i := 0; i &lt;= 10; i++ {
		if ctx.Err() != nil {
			// In case task2 completes first,
			// I want to print this error. How do I reach this block?
			fmt.Println(&quot;Cancelled 1&quot;, ctx.Err())
		}

		fmt.Println(&quot;Task1 ----- &quot;, i)
		time.Sleep(1 * time.Second)
		if i == 5 {
			ch &lt;- 1
		}
	}
}

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	ch := make(chan int)

	go task1(ctx, ch)
	go task2(ctx, ch)
    
	d := &lt;-ch
	cancel() // If either of task1 or task2 completes, invoke the cancel() func

	fmt.Println(&quot;Task which completed first: &quot;, d)
}

答案1

得分: 4

由于您的通道是无缓冲的,在main()函数中只有一个对ch的读取操作,所以task1将在ch <- 1处发生死锁。为了解决这个问题,您可以将其转换为一个选择语句,检查ctx.Done()

if i == 5 {
    select {
        case ch <- 1:
            return
        case <-ctx.Done():
            fmt.Println("Cancelled 1", ctx.Err())
            return
    }
}

您可以在task2中执行相同的操作。

请注意,当main()函数终止时,任何仍然存在的Go协程也将随之终止,无论它们当时正在执行什么操作。如果您不希望如此,您需要提供额外的同步,例如使用sync.WaitGroup,如下所示:

func a(wg *sync.WaitGroup) {
    defer wg.Done()
    time.Sleep(time.Second)
    fmt.Println("a is done!")
}

func b(wg *sync.WaitGroup) {
    defer wg.Done()
    time.Sleep(time.Second*2)
    fmt.Println("b is done, too!")
}

func main() {
    wg := &sync.WaitGroup{}
    wg.Add(2)

    go a(wg)
    go b(wg)

    wg.Wait()

    fmt.Println("Everyone is done! We can terminate without interrupting anyone.")
}
英文:

Since your channel is unbuffered, task1 will deadlock in ch &lt;- 1 since you only have one read on ch in main(). To fix this, you can turn this into a select-statement where you check for ctx.Done():

if i == 5 {
    select {
        case ch &lt;- 1:
            return
        case &lt;-ctx.Done():
            fmt.Println(&quot;Cancelled 1&quot;, ctx.Err())
            return
    }
}

You can do the same in task2.

Do notice that when main() terminates, any go routines still around will terminate along with it, no matter what they were in the process of doing. If you do not want that, you need to provide additional synchronization, for example using a sync.WaitGroup like so:

func a(wg *sync.WaitGroup) {
    defer wg.Done()
    time.Sleep(time.Second)
    fmt.Println(&quot;a is done!&quot;)
}

func b(wg *sync.WaitGroup) {
    defer wg.Done()
    time.Sleep(time.Second*2)
    fmt.Println(&quot;b is done, too!&quot;)
}

func main() {
    wg := &amp;sync.WaitGroup{}
    wg.Add(2)

    go a(wg)
    go b(wg)

    wg.Wait()

    fmt.Println(&quot;Everyone is done! We can terminate without interrupting anyone.&quot;)
}

</details>



huangapple
  • 本文由 发表于 2021年8月2日 14:50:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/68617454.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定