无法循环遍历动态通道的问题。

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

Unable to loop through golang dynamic channels

问题

我想循环遍历菜单选项。然而,它在第一个选项处停止,因为没有带有"default:"的选择是阻塞的,它不知道是否会动态出现更多选项。

下面是有问题的代码:

  1. package main
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io/ioutil"
  6. "log"
  7. "os/exec"
  8. "strings"
  9. "time"
  10. "github.com/getlantern/systray"
  11. "gopkg.in/yaml.v3"
  12. )
  13. var menuItensPtr []*systray.MenuItem
  14. var config map[string]string
  15. var commands []string
  16. func main() {
  17. config = readconfig()
  18. systray.Run(onReady, onExit)
  19. }
  20. func onReady() {
  21. systray.SetIcon(getIcon("assets/menu.ico"))
  22. menuItensPtr = make([]*systray.MenuItem, 0)
  23. commands = make([]string, 0)
  24. for k, v := range config {
  25. menuItemPtr := systray.AddMenuItem(k, k)
  26. menuItensPtr = append(menuItensPtr, menuItemPtr)
  27. commands = append(commands, v)
  28. }
  29. systray.AddSeparator()
  30. // mQuit := systray.AddMenuItem("Quit", "Quits this app")
  31. go func() {
  32. for {
  33. systray.SetTitle("My tray menu")
  34. systray.SetTooltip("https://github.com/evandrojr/my-tray-menu")
  35. time.Sleep(1 * time.Second)
  36. }
  37. }()
  38. go func() {
  39. for{
  40. for i, menuItenPtr := range menuItensPtr {
  41. select {
  42. /// EXECUTION GETS STUCK HERE!!!!!!!
  43. case<-menuItenPtr.ClickedCh:
  44. execute(commands[i])
  45. }
  46. }
  47. // select {
  48. // case <-mQuit.ClickedCh:
  49. // systray.Quit()
  50. // return
  51. // // default:
  52. // }
  53. }
  54. }()
  55. }
  56. func onExit() {
  57. // Cleaning stuff will go here.
  58. }
  59. func getIcon(s string) []byte {
  60. b, err := ioutil.ReadFile(s)
  61. if err != nil {
  62. fmt.Print(err)
  63. }
  64. return b
  65. }
  66. func execute(commands string){
  67. command_array:= strings.Split(commands, " ")
  68. command:=""
  69. command, command_array = command_array[0], command_array[1:]
  70. cmd := exec.Command(command, command_array ...)
  71. var out bytes.Buffer
  72. cmd.Stdout = &out
  73. err := cmd.Run()
  74. if err != nil {
  75. log.Fatal(err)
  76. }
  77. // fmt.Printf("Output %s\n", out.String())
  78. }
  79. func readconfig() map[string]string{
  80. yfile, err := ioutil.ReadFile("my-tray-menu.yaml")
  81. if err != nil {
  82. log.Fatal(err)
  83. }
  84. data := make(map[string]string)
  85. err2 := yaml.Unmarshal(yfile, &data)
  86. if err2 != nil {
  87. log.Fatal(err2)
  88. }
  89. for k, v := range data {
  90. fmt.Printf("%s -> %s\n", k, v)
  91. }
  92. return data
  93. }

下面是可行的临时解决方法:

  1. package main
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io/ioutil"
  6. "log"
  7. "os"
  8. "os/exec"
  9. "path/filepath"
  10. "strings"
  11. "time"
  12. "github.com/getlantern/systray"
  13. "gopkg.in/yaml.v3"
  14. )
  15. var menuItensPtr []*systray.MenuItem
  16. var config map[string]string
  17. var commands []string
  18. var labels []string
  19. var programPath string
  20. func main() {
  21. setProgramPath()
  22. config = readconfig()
  23. time.Sleep(1 * time.Second)
  24. systray.Run(onReady, onExit)
  25. }
  26. func onReady() {
  27. systray.SetIcon(getIcon(filepath.Join(programPath,"assets/menu.ico")))
  28. menuItensPtr = make([]*systray.MenuItem, 0)
  29. i := 0
  30. op0 := systray.AddMenuItem(labels[i], commands[i])
  31. i++
  32. op1 := systray.AddMenuItem(labels[i], commands[i])
  33. i++
  34. op2 := systray.AddMenuItem(labels[i], commands[i])
  35. i++
  36. op3 := systray.AddMenuItem(labels[i], commands[i])
  37. i++
  38. systray.AddSeparator()
  39. mQuit := systray.AddMenuItem("Quit", "Quits this app")
  40. go func() {
  41. for {
  42. systray.SetTitle("My tray menu")
  43. systray.SetTooltip("https://github.com/evandrojr/my-tray-menu")
  44. time.Sleep(1 * time.Second)
  45. }
  46. }()
  47. go func() {
  48. for {
  49. select {
  50. // HERE DOES NOT GET STUCK!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  51. case <-op0.ClickedCh:
  52. execute(commands[0])
  53. case <-op1.ClickedCh:
  54. execute(commands[1])
  55. case <-op2.ClickedCh:
  56. execute(commands[2])
  57. case <-op3.ClickedCh:
  58. execute(commands[3])
  59. case <-mQuit.ClickedCh:
  60. systray.Quit()
  61. return
  62. }
  63. }
  64. }()
  65. }
  66. func onExit() {
  67. // Cleaning stuff will go here.
  68. }
  69. func getIcon(s string) []byte {
  70. b, err := ioutil.ReadFile(s)
  71. if err != nil {
  72. fmt.Print(err)
  73. }
  74. return b
  75. }
  76. func setProgramPath(){
  77. ex, err := os.Executable()
  78. if err != nil {
  79. panic(err)
  80. }
  81. programPath = filepath.Dir(ex)
  82. if err != nil {
  83. fmt.Println(err)
  84. os.Exit(1)
  85. }
  86. }
  87. func execute(commands string) {
  88. command_array := strings.Split(commands, " ")
  89. command := ""
  90. command, command_array = command_array[0], command_array[1:]
  91. cmd := exec.Command(command, command_array...)
  92. var out bytes.Buffer
  93. cmd.Stdout = &out
  94. err := cmd.Run()
  95. if err != nil {
  96. log.Fatal(err)
  97. }
  98. fmt.Printf("Output %s\n", out.String())
  99. }
  100. func readconfig() map[string]string {
  101. yfile, err := ioutil.ReadFile(filepath.Join(programPath,"my-tray-menu.yaml"))
  102. if err != nil {
  103. log.Fatal(err)
  104. }
  105. data := make(map[string]string)
  106. err2 := yaml.Unmarshal(yfile, &data)
  107. if err2 != nil {
  108. log.Fatal(err2)
  109. }
  110. labels = make([]string, 0)
  111. commands = make([]string, 0)
  112. for k, v := range data {
  113. labels = append(labels, k)
  114. commands = append(commands, v)
  115. fmt.Printf("%s -> %s\n", k, v)
  116. }
  117. fmt.Print(len(labels))
  118. return data
  119. }

完整的源代码在这里:
https://github.com/evandrojr/my-tray-menu

英文:

I want to loop through the menu's options. However, it stops at the first option, since the select without "default:" is blocking and it does not know more options will appear dynamically.

Bellow is the broken code:

  1. package main
  2. import (
  3. &quot;bytes&quot;
  4. &quot;fmt&quot;
  5. &quot;io/ioutil&quot;
  6. &quot;log&quot;
  7. &quot;os/exec&quot;
  8. &quot;strings&quot;
  9. &quot;time&quot;
  10. &quot;github.com/getlantern/systray&quot;
  11. &quot;gopkg.in/yaml.v3&quot;
  12. )
  13. var menuItensPtr []*systray.MenuItem
  14. var config map[string]string
  15. var commands []string
  16. func main() {
  17. config = readconfig()
  18. systray.Run(onReady, onExit)
  19. }
  20. func onReady() {
  21. systray.SetIcon(getIcon(&quot;assets/menu.ico&quot;))
  22. menuItensPtr = make([]*systray.MenuItem,0)
  23. commands = make([]string,0)
  24. for k, v := range config {
  25. menuItemPtr := systray.AddMenuItem(k, k)
  26. menuItensPtr = append(menuItensPtr, menuItemPtr)
  27. commands = append(commands, v)
  28. }
  29. systray.AddSeparator()
  30. // mQuit := systray.AddMenuItem(&quot;Quit&quot;, &quot;Quits this app&quot;)
  31. go func() {
  32. for {
  33. systray.SetTitle(&quot;My tray menu&quot;)
  34. systray.SetTooltip(&quot;https://github.com/evandrojr/my-tray-menu&quot;)
  35. time.Sleep(1 * time.Second)
  36. }
  37. }()
  38. go func() {
  39. for{
  40. for i, menuItenPtr := range menuItensPtr {
  41. select {
  42. /// EXECUTION GETS STUCK HERE!!!!!!!
  43. case&lt;-menuItenPtr.ClickedCh:
  44. execute(commands[i])
  45. }
  46. }
  47. // select {
  48. // case &lt;-mQuit.ClickedCh:
  49. // systray.Quit()
  50. // return
  51. // // default:
  52. // }
  53. }
  54. }()
  55. }
  56. func onExit() {
  57. // Cleaning stuff will go here.
  58. }
  59. func getIcon(s string) []byte {
  60. b, err := ioutil.ReadFile(s)
  61. if err != nil {
  62. fmt.Print(err)
  63. }
  64. return b
  65. }
  66. func execute(commands string){
  67. command_array:= strings.Split(commands, &quot; &quot;)
  68. command:=&quot;&quot;
  69. command, command_array = command_array[0], command_array[1:]
  70. cmd := exec.Command(command, command_array ...)
  71. var out bytes.Buffer
  72. cmd.Stdout = &amp;out
  73. err := cmd.Run()
  74. if err != nil {
  75. log.Fatal(err)
  76. }
  77. // fmt.Printf(&quot;Output %s\n&quot;, out.String())
  78. }
  79. func readconfig() map[string]string{
  80. yfile, err := ioutil.ReadFile(&quot;my-tray-menu.yaml&quot;)
  81. if err != nil {
  82. log.Fatal(err)
  83. }
  84. data := make(map[string]string)
  85. err2 := yaml.Unmarshal(yfile, &amp;data)
  86. if err2 != nil {
  87. log.Fatal(err2)
  88. }
  89. for k, v := range data {
  90. fmt.Printf(&quot;%s -&gt; %s\n&quot;, k, v)
  91. }
  92. return data
  93. }

Bellow is the ugly workaround that works:

  1. package main
  2. import (
  3. &quot;bytes&quot;
  4. &quot;fmt&quot;
  5. &quot;io/ioutil&quot;
  6. &quot;log&quot;
  7. &quot;os&quot;
  8. &quot;os/exec&quot;
  9. &quot;path/filepath&quot;
  10. &quot;strings&quot;
  11. &quot;time&quot;
  12. &quot;github.com/getlantern/systray&quot;
  13. &quot;gopkg.in/yaml.v3&quot;
  14. )
  15. var menuItensPtr []*systray.MenuItem
  16. var config map[string]string
  17. var commands []string
  18. var labels []string
  19. var programPath string
  20. func main() {
  21. setProgramPath()
  22. config = readconfig()
  23. time.Sleep(1 * time.Second)
  24. systray.Run(onReady, onExit)
  25. }
  26. func onReady() {
  27. systray.SetIcon(getIcon(filepath.Join(programPath,&quot;assets/menu.ico&quot;)))
  28. menuItensPtr = make([]*systray.MenuItem, 0)
  29. i := 0
  30. op0 := systray.AddMenuItem(labels[i], commands[i])
  31. i++
  32. op1 := systray.AddMenuItem(labels[i], commands[i])
  33. i++
  34. op2 := systray.AddMenuItem(labels[i], commands[i])
  35. i++
  36. op3 := systray.AddMenuItem(labels[i], commands[i])
  37. i++
  38. systray.AddSeparator()
  39. mQuit := systray.AddMenuItem(&quot;Quit&quot;, &quot;Quits this app&quot;)
  40. go func() {
  41. for {
  42. systray.SetTitle(&quot;My tray menu&quot;)
  43. systray.SetTooltip(&quot;https://github.com/evandrojr/my-tray-menu&quot;)
  44. time.Sleep(1 * time.Second)
  45. }
  46. }()
  47. go func() {
  48. for {
  49. select {
  50. // HERE DOES NOT GET STUCK!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  51. case &lt;-op0.ClickedCh:
  52. execute(commands[0])
  53. case &lt;-op1.ClickedCh:
  54. execute(commands[1])
  55. case &lt;-op2.ClickedCh:
  56. execute(commands[2])
  57. case &lt;-op3.ClickedCh:
  58. execute(commands[3])
  59. case &lt;-mQuit.ClickedCh:
  60. systray.Quit()
  61. return
  62. }
  63. }
  64. }()
  65. }
  66. func onExit() {
  67. // Cleaning stuff will go here.
  68. }
  69. func getIcon(s string) []byte {
  70. b, err := ioutil.ReadFile(s)
  71. if err != nil {
  72. fmt.Print(err)
  73. }
  74. return b
  75. }
  76. func setProgramPath(){
  77. ex, err := os.Executable()
  78. if err != nil {
  79. panic(err)
  80. }
  81. programPath = filepath.Dir(ex)
  82. if err != nil {
  83. fmt.Println(err)
  84. os.Exit(1)
  85. }
  86. }
  87. func execute(commands string) {
  88. command_array := strings.Split(commands, &quot; &quot;)
  89. command := &quot;&quot;
  90. command, command_array = command_array[0], command_array[1:]
  91. cmd := exec.Command(command, command_array...)
  92. var out bytes.Buffer
  93. cmd.Stdout = &amp;out
  94. err := cmd.Run()
  95. if err != nil {
  96. log.Fatal(err)
  97. }
  98. fmt.Printf(&quot;Output %s\n&quot;, out.String())
  99. }
  100. func readconfig() map[string]string {
  101. yfile, err := ioutil.ReadFile(filepath.Join(programPath,&quot;my-tray-menu.yaml&quot;))
  102. if err != nil {
  103. log.Fatal(err)
  104. }
  105. data := make(map[string]string)
  106. err2 := yaml.Unmarshal(yfile, &amp;data)
  107. if err2 != nil {
  108. log.Fatal(err2)
  109. }
  110. labels = make([]string, 0)
  111. commands = make([]string, 0)
  112. for k, v := range data {
  113. labels = append(labels, k)
  114. commands = append(commands, v)
  115. fmt.Printf(&quot;%s -&gt; %s\n&quot;, k, v)
  116. }
  117. fmt.Print(len(labels))
  118. return data
  119. }

Full source code here:
https://github.com/evandrojr/my-tray-menu

答案1

得分: 1

select 语句用于“选择一组可能的发送或接收操作中的哪一个将继续进行”。规范说明了如何进行选择:

>如果有一个或多个通信可以继续进行,将通过均匀伪随机选择来选择一个可以继续进行的通信。否则,如果存在默认情况,则选择该情况。如果没有默认情况,则“select”语句将阻塞,直到至少有一个通信可以继续进行。

你的工作示例:

  1. select {
  2. case &lt;-op0.ClickedCh:
  3. execute(commands[0])
  4. case &lt;-op1.ClickedCh:
  5. execute(commands[1])
  6. // ...
  7. }

成功地使用 select 来在提供的选项之间进行选择。然而,如果你只传递一个选项,例如:

  1. select {
  2. case&lt;-menuItenPtr.ClickedCh:
  3. execute(commands[i])
  4. }
  5. }

select 语句将阻塞,直到 &lt;-menuItenPtr.ClickedCh 准备就绪(例如,接收到了某些内容)。这实际上与不使用 select 是一样的:

  1. &lt;-menuItenPtr.ClickedCh:
  2. execute(commands[i])

你期望的结果可以通过提供一个默认选项来实现:

  1. select {
  2. case&lt;-menuItenPtr.ClickedCh:
  3. execute(commands[i])
  4. }
  5. default:
  6. }

根据上面规范中的引用,如果其他选项都无法继续进行,将选择 default 选项。虽然这可能会起作用,但并不是一个很好的解决方案,因为你实际上得到了:

  1. for {
  2. // 检查事件是否发生(非阻塞)
  3. }

这将不必要地占用 CPU 时间,因为它不断循环检查事件。一个更好的解决方案是启动一个 goroutine 来监视每个通道:

  1. for i, menuItenPtr := range menuItensPtr {
  2. go func(c chan struct{}, cmd string) {
  3. for range c { execute(cmd) }
  4. }(menuItenPtr.ClickedCh, commands[i])
  5. }
  6. // 启动另一个 goroutine 来处理退出

上述方法可能会起作用,但可能会导致 execute 并发调用(如果你的代码不是线程安全的,可能会引发问题)。解决这个问题的一种方法是使用“扇入”模式(由 @kostix 提出,并在 Rob Pike 视频 中由 @John 提到):

  1. cmdChan := make(chan int)
  2. for i, menuItenPtr := range menuItensPtr {
  3. go func(c chan struct{}, cmd string) {
  4. for range c { cmdChan &lt;- cmd }
  5. }(menuItenPtr.ClickedCh, commands[i])
  6. }
  7. go func() {
  8. for {
  9. select {
  10. case cmd := &lt;- cmdChan:
  11. execute(cmd) // 处理命令
  12. case &lt;-mQuit.ClickedCh:
  13. systray.Quit()
  14. return
  15. }
  16. }
  17. }()

注意:上面的所有代码直接输入到问题中,请将其视为伪代码!

英文:

select "chooses which of a set of possible send or receive operations will proceed". The spec sets out how this choice is made:

>If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection. Otherwise, if there is a default case, that case is chosen. If there is no default case, the "select" statement blocks until at least one of the communications can proceed.

Your working example:

  1. select {
  2. case &lt;-op0.ClickedCh:
  3. execute(commands[0])
  4. case &lt;-op1.ClickedCh:
  5. execute(commands[1])
  6. // ...
  7. }

uses select successfully to choose between one of the offered options. However if you pass a single option e.g.

  1. select {
  2. case&lt;-menuItenPtr.ClickedCh:
  3. execute(commands[i])
  4. }
  5. }

The select will block until &lt;-menuItenPtr.ClickedCh is ready to proceed (e.g. something is received). This is effectively the same as not using a select:

  1. &lt;-menuItenPtr.ClickedCh:
  2. execute(commands[i])

The result you were expecting can be achieved by providing a default option:

  1. select {
  2. case&lt;-menuItenPtr.ClickedCh:
  3. execute(commands[i])
  4. }
  5. default:
  6. }

As per the quote from the spec above the default option will be chosen if none of the other options can proceed. While this may work it's not a very good solution because you effectively end up with:

  1. for {
  2. // Check if event happened (not blocking)
  3. }

This will tie up CPU time unnecessarily as it continually loops checking for events. A better solution would be to start a goroutine to monitor each channel:

  1. for i, menuItenPtr := range menuItensPtr {
  2. go func(c chan struct{}, cmd string) {
  3. for range c { execute(cmd) }
  4. }(menuItenPtr.ClickedCh, commands[i])
  5. }
  6. // Start another goroutine to handle quit

The above will probably work but does lead to the possibility that execute will be called concurrently (which might cause issues if your code is not threadsafe). One way around this is to use the "fan in" pattern (as suggested by @kostix and in the Rob Pike video suggested by @John); something like:

  1. cmdChan := make(chan int)
  2. for i, menuItenPtr := range menuItensPtr {
  3. go func(c chan struct{}, cmd string) {
  4. for range c { cmdChan &lt;- cmd }
  5. }(menuItenPtr.ClickedCh, commands[i])
  6. }
  7. go func() {
  8. for {
  9. select {
  10. case cmd := &lt;- cmdChan:
  11. execute(cmd) // Handle command
  12. case &lt;-mQuit.ClickedCh:
  13. systray.Quit()
  14. return
  15. }
  16. }
  17. }()

note: all code above entered directly into the question so please treat as pseudo code!

huangapple
  • 本文由 发表于 2022年8月7日 01:08:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/73261794.html
匿名

发表评论

匿名网友

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

确定