除了 goroutines 之外,我如何使用微调器以使输出不会相互覆盖?

How can I use a spinner in addition to goroutines so that the output is not overriding each other?

我想做的是使用微调器来指示几个 goroutine 中的工作进度。我遇到的问题是微调器消息和作业完成消息都记录在同一行中。我想问的是如何将微调器固定到底部,以便微调器状态不会妨碍记录的消息本身?

我得到的输出是

a
⡿ 1/26c
⣽ 2/26b
⣯ 3/26e
⢿ 4/26d
⣾ 5/26g
⣟ 6/26f

我想得到的是

a
b
c
d
e
f
⣟ 6/26

临时工作代码。 (我意识到我的go例程可能不是很优雅,但这只是一个例子)。

package main

import (
    "fmt"
    "os"
    "os/signal"
    "sync"
    "syscall"
    "time"

    "github.com/theckman/yacspin"
)

func main() {
    // create a spinner
    spinner, err := createSpinner()
    if err != nil {
        fmt.Printf("failed to make spinner from config struct: %v\n", err)
        os.Exit(1)
    }

    // start the spinner
    if err := spinner.Start(); err != nil {
        panic(err)
    }

    wg := &sync.WaitGroup{}
    wg.Add(1)
    var (
        total   int
        current int
    )

    spinnerCh := make(chan int, 0)
    data := make(chan string)

    // only want one go routine at a time but this is not important
    max := make(chan struct{}, 1)

    go func(s *yacspin.Spinner) {
        // tried moving this into the worker goroutine also, but same effect
        for range spinnerCh {
            current += 1
            if err := s.Pause(); err != nil {
                panic(err)
            }
            s.Message(fmt.Sprintf("%d/%d", current, total))
            if err := s.Unpause(); err != nil {
                panic(err)
            }
        }
    }(spinner)

    go func() {
        defer wg.Done()
        for d := range data {
            wg.Add(1)
            go func(wg *sync.WaitGroup, d string) {
                max <- struct{}{}
                defer func() {
                    <-max
                }()

                // function is doing work and printing the result once done.
                fmt.Println(d)

                // sends a value to the spinner go routine so that it can show
                // the updated count
                time.Sleep(500 * time.Millisecond)
                spinnerCh <- 1
                wg.Done()
            }(wg, d)
        }
    }()

    // simulate queing some work
    ss := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"}
    for _, s := range ss {
        data <- s
    }
    total = len(ss)

    close(data)
    wg.Wait()
    close(spinnerCh)
}

func createSpinner() (*yacspin.Spinner, error) {
    // build the configuration, each field is documented
    cfg := yacspin.Config{
        Frequency: 100 * time.Millisecond,
        CharSet:   yacspin.CharSets[11],
        Suffix:    " ", // puts a least one space between the animating spinner and the Message
        // Message:           "collecting files",
        SuffixAutoColon:   true,
        ColorAll:          true,
        Colors:            []string{"fgYellow"},
        StopCharacter:     "✓",
        StopColors:        []string{"fgGreen"},
        StopMessage:       "done",
        StopFailCharacter: "✗",
        StopFailColors:    []string{"fgRed"},
        StopFailMessage:   "failed",
    }

    s, err := yacspin.New(cfg)
    if err != nil {
        return nil, fmt.Errorf("failed to make spinner from struct: %w", err)
    }

    return s, nil
}

func stopOnSignal(spinner *yacspin.Spinner) {
    // ensure we stop the spinner before exiting, otherwise cursor will remain
    // hidden and terminal will require a `reset`
    sigCh := make(chan os.Signal, 1)
    signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
    go func() {
        <-sigCh

        spinner.StopFailMessage("interrupted")

        // ignoring error intentionally
        _ = spinner.StopFail()

        os.Exit(0)
    }()
}

根据您最初的问题,以下是不在行中显示微调器的解决方案:

  1. 引入一个变量

pauseItForAMoment := false 这将有助于忽略在您的一个 goroutine 中是否暂停。

func main() {
    pauseItForAMoment := false

goroutine 看起来像这样:

        // tried moving this into the worker goroutine also, but same effect
        for range spinnerCh {
            current += 1
            if !pauseItForAMoment {
                if err := s.Pause(); err != nil {
                    panic(err)
                }
                s.Message(fmt.Sprintf("%d/%d", current, total))
                if err := s.Unpause(); err != nil {
                    panic(err)
                }
            }
        }
    }(spinner)
  1. 打印时停止和启动微调器 d
                pauseItForAMoment = true
                spinner.Prefix(d)
                spinner.Stop()
                // fmt.Println(d)
                spinner.Start()
                pauseItForAMoment = false
  1. 将几个微调器配置值更改为:
        StopCharacter:   " ",
        // StopMessage:       "done",

请注意,您的 goroutine 并不完全正确地按照您已经知道的顺序打印它们,希望您能解决这个问题。

输出如下所示:

这里是代码,如果有什么不清楚的话https://go.dev/play/p/0lOza3aapf2

我更喜欢像下面这样显示输出,代码是 https://go.dev/play/p/W8Y1kwNqphl(只是我的 2cents ;-) 如果你不需要它,请忽略它)

这里是图书馆的作者。根据您在 GitHub issue you opened 中分享的片段,我认为您只需将 StopCharacter 值设置为空字符串而不是 " "。它让我这样渲染...

这是 Go Playground 上的the code,因为我太笨不知道如何在不丢失格式的情况下复制和粘贴它。

希望对您有所帮助!