I'm working on a console music player in Go
. Whenever the user selects and plays an album, I launch a goroutine to loop over a playlist.
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()
}
}
I need a way of canceling this goroutine when the user selects a new album. I'm using context.Context
to do so (but I'm not convinced it's the best solution).
I create the ctx
ctx, cancel := context.WithCancel(context.Background())
So in the UI event handler, the play()
func
will cancel()
the goroutine.
This works once I check inside the update UI for
loop:
select {
case <-ctx.Done():
return err
default:
time.Sleep(50 * time.Millisecond)
}
Then the channel ctx.Done()
is closed and the next albums played will always return instead of loop.
Is there a way to recancel a context.Context
?
If not, is there a better way to cancel this goroutine (and the following goroutines)?
Alternatively I've tried to use waitgroups,
go func() {
wg.Wait()
wg.Add(1)
err = playAlbum(
done,
player,
*albums[s],
list,
status,
)
wg.Done()
// handle err
}()
But then I get a sync: WaitGroup is reused before previous Wait has returned
panic
What about using a channel to cancel the goroutine?
select {
case <-chClose:
return
default:
}
Your cancel() call could simply close the channel:
close(chClose)
but then you cannot close it again! So you need to make sure your new album has a new chClose. Depending on your code structure this might be the cleaner solution.
Alternatively you can just send a value on chClose to initiate a stop of the go routine:
chClose <- 1
You can do that as often as you want.
Note that if there is no goroutine listening, this will block (or if you have a buffer, you will end up closing routines that have not even startet yet. --> You need a clean architecture!!)