【问题标题】:Non-blocking read of a byte from stdin从标准输入非阻塞读取一个字节
【发布时间】:2017-12-12 20:04:28
【问题描述】:

我想通过以下方式从 C# 单声道进程的标准输入(标准输入)中读取一个字节(或字符):

  • 如果已经有字节可用于从标准输入读取,则返回第一个
  • 如果没有可用于从标准输入读取的字节,则继续进行而不等待任何内容或阻塞
  • 如果有线程或异步,它应该是简洁且可证明正确的按需获取单个字节的直接解决方案
  • stdin 可能是管道或文件,所以 Console.KeyAvailable 不会这样做

谢谢。

【问题讨论】:

  • 没有独立于平台的方式来执行异步 stdin I/O,这就是 BCL 不尝试的原因。 “标准输入流上的读取操作同步执行。也就是说,它们阻塞,直到指定的读取操作完成。”可以在 Mono 上实现吗?可能,如果您喜欢 P/Invoke 和 #if 来处理各种平台。不过,与启动线程来包装同步操作相比,几乎可以肯定不值得这样做(并使用Console.IsInputRedirected 来区分这种情况)。
  • @JeroenMostert 编写更多代码正是我想要避免的。我将举一个非常简洁的例子来说明如何以最短的方式完成它。这个想法是为了避免在问题上花费任何重要的代码或精力。来吧,2017年了!而且我们无法在没有线程或阻塞的情况下从流中读取字节!?
  • 确实是 2017 年,但跨平台、非阻塞控制台 I/O 并不是一个需求量很大的功能——从来没有。时尚一直是控制台应用程序,它以同步、单线程的方式平静地处理其输入。如果您满足于轮询而不是真正的异步(就像您一样),那么在 Unix 和 Win32 上都相当容易做到(当然,同样的方式,这太容易了),但 .NET 更适合最小的公分母,这没有帮助。我无法在 10 分钟内想出一个简单的解决方案。不是说那里没有一个。
  • 在思考的同时想出了一个不那么简单的解决方案。我希望有更好的答案,也许这会激励某人。
  • @JeroenMostert 轮询不是另一个线程的事件循环中的一个选项,在该循环中应该发生来自不同方向的事件的多路复用。

标签: c# mono stdin nonblocking


【解决方案1】:

如果您的平台具有System.Collections.Concurrent.BlockingCollection 的实现,这里有一种简单但不完全有效的方法:

class ConsoleReader {
  private readonly BlockingCollection<int> buffer = new BlockingCollection<int>(1);
  private readonly Thread readThread;
  public ConsoleReader() {
    readThread = new Thread(() => {
      if (Console.IsInputRedirected) {
        int i;
        do {
          i = Console.Read();
          buffer.Add(i);
        } while (i != -1);
      } else {
        while (true) { 
          var consoleKeyInfo = Console.ReadKey(true);
          if (consoleKeyInfo.KeyChar == 0) continue;  // ignore dead keys
          buffer.Add(consoleKeyInfo.KeyChar);
        }
      }
    });
  }

  public void Start() {
    readThread.Start();
  }

  public int? Next {
    get {
      int result;
      return buffer.TryTake(out result, 0) ? result : default(int?);
    }
  }
}

免责声明:此答案实际上并未在 Mono 上进行过测试,仅在完整的 .NET 平台上进行过短暂测试。 您可以使用Task 而不是Thread、适当的取消等来增加它的趣味性,但这不会改变显式等待同步操作的中心低效率(Console.ReadConsole.ReadKey 都是) . .NET 本身不提供便携式异步控制台 I/O。自己跨平台实现(使用操作系统功能)是可能的,但绝对不是微不足道的。

【讨论】:

  • 其实有Console.KeyAvailable形式的可移植异步控制台I/O。给我带来麻烦的是输入重定向场景 - 从输入重定向(== 文件)流进行非阻塞读取。
  • @alamar:Console.KeyAvailable确实是non-blocking,和asynchronous不是一回事,在.NET——没有Console.BeginReadKey()Console.ReadKeyAsync()。然而,Stream.BeginRead()Console.OpenStandardInput()。控制台流仍然是同步的,但.BeginRead() 将始终提供异步同步包装器。这可能会达到你想要的。
  • @alamar:请注意,从控制台读取时,Console.Read() 和输入流都不会返回任何内容,直到读取了整行,即使您只要求一个字符。这就是为什么.OpenStandardInput()/.BeginRead() 不是通用解决方案的原因。但是您可以将其与.IsInputRedirected 结合使用。
  • 非阻塞是我们想要的,而不是异步的。这个想法是在需要的地方获得价值。异步只能在不再有用的上下文中交付价值。当然,我可以在某处存储该值,但这听起来已经像很多基础设施了。但是,如果您可以将包括存储在内的解决方案组合在一起,它会更短/更直接吗? Console.ReadKey 很高兴地从控制台读取单个字节,而无需等待整行。
  • 我的解决方案存储——最后读取的密钥存储在BlockingCollection中,直到你调用.Next才会被检索。如果没有密钥(还),它不会返回任何东西。如果输入已从流重定向,.Next 将始终返回一个字符,直到流完成。所以要么这正是你想要的,要么我只是不明白你的场景,在这种情况下,我鼓励你自己玩这个代码。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-26
  • 2011-12-27
  • 2011-09-18
  • 2010-11-19
相关资源
最近更新 更多