取消使用上下文读取golang os.Stdin

dhxwm5r4  于 2023-06-03  发布在  Go
关注(0)|答案(2)|浏览(236)

我希望能够取消一个os.stdin读使用上下文,这是不可能的。通常,关闭文件句柄来完成取消操作,但我不想关闭os.stdin
可能的解决办法是:

  • 是否可以确定标准输入读取是否会阻塞?
  • 线程可以像pthreads那样终止吗?
  • 是否应将os.stdin转发到另一个可以关闭的文件句柄?

这是我得到的,丑陋的是scannerThread在上下文取消时仍然运行:

// Keystrokes emits keystroke events
// on g0.Context() shutdown, scannerThread is left running until the next newline
// the lines channel is never closed
func Keystrokes(lines chan<- string, g0 parl.Go) {
  var err error
  defer g0.Done(err)
  defer parl.Recover(parl.Annotation(), &err, parl.NoOnError)

  // stdio.Scan cannot be terminated, so let that thread terminate whenever
  var scanLines parl.NBChan[string]
  go scannerThread(scanLines.Send, g0.Context())

  // consume scannerThread output
  scannerCh := scanLines.Ch()
  done := g0.Context().Done()
  for {
    select {
    case line := <-scannerCh:
      lines <- line
    case <-done:
      return // canceled by context exit
    }
  }
}

// scannerThread reads from os.Stdin and therefore cannot be cancelled.
// send is a non-blocking send function.
// ctx indicates shutdown effective on next os.Stdin newline.
func scannerThread(send func(string), ctx context.Context) {
  var err error
  defer parl.Recover(parl.Annotation(), &err, parl.Infallible)

  scanner := bufio.NewScanner(os.Stdin)
  for scanner.Scan() { // scanner is typically stuck here
    if ctx.Err() != nil {
      return // terminated via context
    }
    send(scanner.Text())
  }

  // scanner had error
  err = scanner.Err()
}
uurv41yg

uurv41yg1#

答案是读os.Stdin非阻塞,适用于Linux macOS
我记得,Stdin有一个阻止关闭它的块
非阻塞的os.Stdin.Read()在一个完整的行可用时返回字节,否则返回n==
以下是如何设置非阻塞:

err = unix.SetNonblock(int(os.Stdin.Fd()), true)

也可以读取非阻塞状态:

flags, err := unix.FcntlInt(os.Stdin.Fd(), unix.F_GETFL, 0)
wasSet = flags&unix.O_NONBLOCK != 0
ffscu2ro

ffscu2ro2#

没有办法做到这一点开箱即用。您需要依靠利用OS功能的特殊实现。
有一个图书馆显然可以做到这一点:https://github.com/muesli/cancelreader
下面是它的工作原理:

r, err := cancelreader.NewReader(file)
if err != nil {
    // handle error
    ...
}

// cancel after five seconds
go func() {
    time.Sleep(5 * time.Second)
    r.Cancel()
}()

// keep reading
for {
    var buf [1024]byte
    _, err := r.Read(buf[:])

    if errors.Is(err, cancelreader.ErrCanceled) {
        fmt.Println("canceled!")
        break
    }
    if err != nil {
        // handle other errors
        ...
    }

    // handle data
    ...
}

它以一种相对跨平台的方式完成了你所需要的一切。

相关问题