我正在
Go
中开发控制台音乐播放器。每当用户选择并播放专辑时,我都会启动一个 goroutine 来循环播放列表。
playlist := make([]*Media, 0)
for _, path := range album.Paths {
media, err := NewMediaFromPath(path)
// return err
playlist = append(playlist, media)
}
for idx := range playlist {
player.SetMedia(playlist[idx])
err = player.Play()
// check err
status, err := player.MediaState()
// check err
for status != MediaEnded && status != MediaStopped {
// update UI and check player status
// loop until the song finishes
status, err = player.MediaState()
}
}
当用户选择新专辑时,我需要一种方法来取消这个goroutine。我正在使用
context.Context
来执行此操作(但我不相信这是最好的解决方案)。
我创建了
ctx
ctx, cancel := context.WithCancel(context.Background())
因此,在 UI 事件处理程序中,
play()
func
将 cancel()
goroutine。
一旦我检查更新 UI
for
循环,这就会起作用:
select {
case <-ctx.Done():
return err
default:
time.Sleep(50 * time.Millisecond)
}
然后频道
ctx.Done()
关闭,下一个播放的专辑将始终返回而不是循环。
有办法取消
context.Context
吗?
如果没有,有没有更好的方法来取消这个goroutine(以及后面的goroutine)?
或者我尝试使用等待组,
go func() {
wg.Wait()
wg.Add(1)
err = playAlbum(
done,
player,
*albums[s],
list,
status,
)
wg.Done()
// handle err
}()
但后来我感到
sync: WaitGroup is reused before previous Wait has returned
恐慌
使用通道取消goroutine怎么样?
select {
case <-chClose:
return
default:
}
您的 cancel() 调用可以简单地关闭通道:
close(chClose)
但是你无法再次关闭它!所以你需要确保你的新专辑有一个新的 chClose。根据您的代码结构,这可能是更干净的解决方案。
或者,您可以在 chClose 上发送一个值来启动 go 例程的停止:
chClose <- 1
您可以根据需要多次这样做。
请注意,如果没有 goroutine 监听,这将阻塞(或者如果你有缓冲区,你最终将关闭尚未启动的例程。--> 你需要一个干净的架构!!)
您也可以包装上下文,如果这对您的用例有帮助的话:
https://go.dev/play/p/XvvitAmhkVt
package main
import (
"context"
"fmt"
)
func IsCanceled(ctx context.Context) bool {
return ctx.Err() != nil
}
func main() {
a, cancelA := context.WithCancel(context.Background())
b, cancelB := context.WithCancel(a)
c, cancelC := context.WithCancel(b)
fmt.Println(IsCanceled(a), IsCanceled(b), IsCanceled(c))
cancelB()
fmt.Println(IsCanceled(a), IsCanceled(b), IsCanceled(c))
cancelA()
fmt.Println(IsCanceled(a), IsCanceled(b), IsCanceled(c))
cancelC()
fmt.Println(IsCanceled(a), IsCanceled(b), IsCanceled(c))
}