简体   繁体   English

从SerialPort同步读取单个字节?

[英]Reading single bytes from SerialPort synchronously?

I'm attempting to read from a serial port a byte at a time. 我正在尝试一次从串行端口读取一个字节。 I've got the following code in my Console app: 我的控制台应用程序中包含以下代码:

// Open the serial port in 115200,8N1
using (SerialPort serialPort = new SerialPort("COM1", 115200,
                                              Parity.None, 8,
                                              StopBits.One))
{
    serialPort.Open();

    for (; ; )
    {
        int result = serialPort.ReadByte();
        if (result < 0)
            break;

        Console.WriteLine(result);
    }
}

I'm expecting this to loop round, dumping the bytes received to the screen (ignore for a moment that they'll be printed as integers; I'll deal with that later). 我期望这会循环,将接收到的字节转储到屏幕上(暂时忽略它们将被打印为整数;稍后再处理)。

However, it just blocks on the ReadByte call and nothing happens. 但是,它只是阻塞了ReadByte调用,没有任何反应。

I know that my serial device is working: if I use Tera Term, I see the data. 我知道我的串行设备正在工作:如果我使用Tera Term,我会看到数据。 If I use the DataReceived event, and call SerialPort.ReadExisting , then I can see the data. 如果我使用DataReceived事件,并调用SerialPort.ReadExisting ,那么我可以看到数据。

However, I'm not bothered about performance (at least, not yet), and the protocol I'm implementing works better when dealt with synchronously. 但是,我并不担心性能(至少现在还没有),并且正在执行的协议在同步处理时效果更好。

So: what am I doing wrong? 所以:我在做什么错? Why doesn't ReadByte return? 为什么ReadByte不返回?

You could make the asynchronous behavior look synchronous by doing something like this, and calling WaitForData() before each read: 通过执行以下操作,并在每次读取之前调用WaitForData() ,可以使异步行为看起来是同步的:

static SerialPort port;
static AutoResetEvent dataArrived = new AutoResetEvent(false);

static void Main(string[] args) {
  port = new SerialPort(...);
  port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);
  port.Open();
  WaitForData(1000);
  int data = port.ReadByte();
  Console.WriteLine(data);
  Console.ReadKey();
}

static void WaitForData(int millisecondsTimeout) {
  dataArrived.WaitOne(millisecondsTimeout);
}

static void port_DataReceived(object sender, SerialDataReceivedEventArgs e) {
  dataArrived.Set();      
}

This answer isn't as "correct" as finding and resolving the underlying problem, but could be the basis of a workaround. 这个答案不像发现和解决根本问题那样“正确”,但是可以作为解决方法的基础。

I've seen some strange things with the SerialPort class, including the behavior you described. 我已经看到了SerialPort类的一些奇怪之处,包括您描述的行为。 Keep in mind that the DataReceived event gets called on a secondary thread (see MSDN ). 请记住,在辅助线程上调用了DataReceived事件(请参见MSDN )。 You can get slightly better performance using lock() semantics with Monitor.Wait() and .Pulse(),as described here 您可以使用锁()的语义与Monitor.Wait()和.Pulse()获得稍好的性能,如所描述这里

If you're lazy, you could also try inserting a Thread.Sleep() line (eg 200ms) right before your call to ReadByte to see if it makes a difference. 如果您很懒,也可以尝试在调用ReadByte之前插入Thread.Sleep()行(例如200ms),以查看它是否有所作为。 Also I could have sworn I once saw a case where a SerialPort that was blocking on ReadByte() in a Console app was ported to a WinForms app with no meaningful code changes and the problem went away. 同样,我可能发誓我曾经看到过这样一种情况,即在控制台应用程序中阻塞ReadByte()的SerialPort被移植到WinForms应用程序而没有有意义的代码更改,问题就消失了。 Didn't have a chance to thoroughly investigate, but you could see if you have any better luck under WinForms and then troubleshoot from there. 没有机会进行彻底调查,但是您可以查看在WinForms下是否有更好的运气,然后从那里进行故障排除。

This answer is a little late, but I figured I'd chime in for the next person who gets stumped on this issue. 这个答案有点晚了,但我想我会为下一个在这个问题上陷入困境的人而高兴。

EDIT: Here's a handy WaitForBytes(count, timeout) extension method that does a good job of filtering out the "infinite blocking" behavior you described. 编辑:这是一个方便的WaitForBytes(count, timeout)扩展方法,可以很好地过滤掉您描述的“无限阻止”行为。

Usage is: port.WaitForBytes(1) to wait for 1 byte of data to arrive. 用法是: port.WaitForBytes(1)等待1个字节的数据到达。 Or for less overhead, use SerialPortWatcher.WaitForBytes(n) instead. 或者为了减少开销,请使用SerialPortWatcher.WaitForBytes(n)

using System;
using System.Diagnostics;
using System.IO.Ports;
using System.Threading;

public static class SerialPortExtensions {

  /// <summary>
  /// Wait for a specified number of bytes to arrive on the serial port, or until a timeout occurs.
  /// </summary>
  /// <param name="port">Serial port on which bytes are expected to arrive.</param>
  /// <param name="count">Number of bytes expected.</param>
  /// <param name="millisecondsTimeout">Maximum amount of time to wait.</param>
  /// <exception cref="TimeoutException">Thrown if <paramref name="count"/> bytes are not received
  /// within <paramref name="millisecondsTimeout"/> milliseconds.</exception>
  /// <exception cref="ArgumentNullException">Thrown if <paramref name="port"/> is null.</exception>
  /// <exception cref="ArgumentOutOfRangeException">Thrown if either <paramref name="count"/> or
  /// <paramref name="millisecondsTimeout"/> is less than zero.</exception>
  /// <exception cref="InvalidOperationException">Thrown if the serial port is closed.</exception>
  /// <remarks>This extension method is intended only as an ad-hoc aid.  If you're using it a lot,
  /// then it's recommended for performance reasons to instead instantiate a
  /// <see cref="SerialPortWatcher"/> instance for the lifetime of your SerialPort.</remarks>
  public static void WaitForBytes(this SerialPort port, int count, int millisecondsTimeout) {

    if (port == null) throw new ArgumentNullException("port");
    if (port.BytesToRead >= count) return;

    using (var watcher = new SerialPortWatcher(port)) {
      watcher.WaitForBytes(count, millisecondsTimeout);
    }

  }

  /// <summary>
  /// Wait for a specified number of bytes to arrive on the serial port, or until a timeout occurs.
  /// </summary>
  /// <param name="port">Serial port on which bytes are expected to arrive.</param>
  /// <param name="count">Number of bytes expected.</param>
  /// <exception cref="ArgumentNullException">Thrown if <paramref name="port"/> is null.</exception>
  /// <exception cref="ArgumentOutOfRangeException">Thrown if either <paramref name="count"/> or
  /// <paramref name="millisecondsTimeout"/> is less than zero.</exception>
  /// <exception cref="InvalidOperationException">Thrown if the serial port is closed.</exception>
  /// <exception cref="TimeoutException">Thrown if <paramref name="count"/> bytes are not received
  /// within the number of milliseconds specified in the <see cref="SerialPort.ReadTimeout"/> property
  /// of <paramref name="port"/>.</exception>
  /// <remarks>This extension method is intended only as an ad-hoc aid.  If you're using it a lot,
  /// then it's recommended for performance reasons to instead instantiate a
  /// <see cref="SerialPortWatcher"/> instance for the lifetime of your SerialPort.</remarks>
  public static void WaitForBytes(this SerialPort port, int count) {
    if (port == null) throw new ArgumentNullException("port");
    WaitForBytes(port, count, port.ReadTimeout);
  }

}

/// <summary>
/// Watches for incoming bytes on a serial port and provides a reliable method to wait for a given
/// number of bytes in a synchronous communications algorithm.
/// </summary>
class SerialPortWatcher : IDisposable {

  // This class works primarilly by watching for the SerialPort.DataReceived event.  However, since
  // that event is not guaranteed to fire, it is neccessary to also periodically poll for new data.
  // The polling interval can be fine-tuned here.  A higher number means less wasted CPU time, while
  // a lower number decreases the maximum possible latency.
  private const int POLL_MS = 30;

  private AutoResetEvent dataArrived = new AutoResetEvent(false);
  private SerialPort port;

  public SerialPortWatcher(SerialPort port) {
    if (port == null) throw new ArgumentNullException("port");
    this.port = port;
    this.port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);
  }

  public void Dispose() {
    if (port != null) {
      port.DataReceived -= port_DataReceived;
      port = null;
    }
    if (dataArrived != null) {
      dataArrived.Dispose();
      dataArrived = null;
    }
  }

  void port_DataReceived(object sender, SerialDataReceivedEventArgs e) {

    // This event will occur on a secondary thread.  Signal the waiting thread (if any).
    // Note: This handler could fire even after we are disposed.

    // MSDN documentation describing DataReceived event:
    // http://msdn.microsoft.com/en-us/library/system.io.ports.serialport.datareceived.aspx

    // Links discussing thread safety and event handlers:
    // https://stackoverflow.com/questions/786383/c-events-and-thread-safety
    // http://www.codeproject.com/Articles/37474/Threadsafe-Events.aspx

    // Note that we do not actually check the SerialPort.BytesToRead property here as it
    // is not documented to be thread-safe.

    if (dataArrived != null) dataArrived.Set();

  }

  /// <summary>
  /// Blocks the current thread until the specified number of bytes have been received from the
  /// serial port, or until a timeout occurs.
  /// </summary>
  /// <param name="count">Number of bytes expected.</param>
  /// <param name="millisecondsTimeout">Maximum amount of time to wait.</param>
  /// <exception cref="ArgumentOutOfRangeException">Thrown if either <paramref name="count"/> or
  /// <paramref name="millisecondsTimeout"/> is less than zero.</exception>
  /// <exception cref="InvalidOperationException">Thrown if the serial port is closed, or if this
  /// <see cref="SerialPortWatcher"/> instance has been disposed.</exception>
  /// <exception cref="TimeoutException">Thrown if <paramref name="count"/> bytes are not received
  /// within the number of milliseconds specified in the <see cref="SerialPort.ReadTimeout"/> property
  /// of <paramref name="port"/>.</exception>
  public void WaitForBytes(int count, int millisecondsTimeout) {

    if (count < 0) throw new ArgumentOutOfRangeException("count");
    if (millisecondsTimeout < 0) throw new ArgumentOutOfRangeException("millisecondsTimeout");
    if (port == null) throw new InvalidOperationException("SerialPortWatcher has been disposed.");
    if (!port.IsOpen) throw new InvalidOperationException("Port is closed");

    if (port.BytesToRead >= count) return;

    DateTime expire = DateTime.Now.AddMilliseconds(millisecondsTimeout);

    // Wait for the specified number of bytes to become available.  This is done primarily by
    // waiting for a signal from the thread which handles the DataReceived event.  However, since
    // that event isn't guaranteed to fire, we also poll for new data every POLL_MS milliseconds.
    while (port.BytesToRead < count) {
      if (DateTime.Now >= expire) {
        throw new TimeoutException(String.Format(
          "Timed out waiting for data from port {0}", port.PortName));
      }
      WaitForSignal();
    }

  }

  // Timeout exceptions are expected to be thrown in this block of code, and are perfectly normal.
  // A separate method is used so it can be marked with DebuggerNonUserCode, which will cause the
  // debugger to ignore these exceptions (even if Thrown is checkmarked under Debug | Exceptions).
  [DebuggerNonUserCode]
  private void WaitForSignal() {
    try {
      dataArrived.WaitOne(POLL_MS);
    } catch (TimeoutException) { }
  }

}

I think your loop breaks at first run (at start), when there is no data in buffer. 我认为当缓冲区中没有数据时,您的循环会在第一次运行(启动时)时中断。

if (result < 0)
    break;

Later loop is not running and you see no data on console. 以后的循环未运行,并且您在控制台上看不到任何数据。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM