簡體   English   中英

從SerialPort同步讀取單個字節?

[英]Reading single bytes from SerialPort synchronously?

我正在嘗試一次從串行端口讀取一個字節。 我的控制台應用程序中包含以下代碼:

// 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);
    }
}

我期望這會循環,將接收到的字節轉儲到屏幕上(暫時忽略它們將被打印為整數;稍后再處理)。

但是,它只是阻塞了ReadByte調用,沒有任何反應。

我知道我的串行設備正在工作:如果我使用Tera Term,我會看到數據。 如果我使用DataReceived事件,並調用SerialPort.ReadExisting ,那么我可以看到數據。

但是,我並不擔心性能(至少現在還沒有),並且正在執行的協議在同步處理時效果更好。

所以:我在做什么錯? 為什么ReadByte不返回?

通過執行以下操作,並在每次讀取之前調用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();      
}

這個答案不像發現和解決根本問題那樣“正確”,但是可以作為解決方法的基礎。

我已經看到了SerialPort類的一些奇怪之處,包括您描述的行為。 請記住,在輔助線程上調用了DataReceived事件(請參見MSDN )。 您可以使用鎖()的語義與Monitor.Wait()和.Pulse()獲得稍好的性能,如所描述這里

如果您很懶,也可以嘗試在調用ReadByte之前插入Thread.Sleep()行(例如200ms),以查看它是否有所作為。 同樣,我可能發誓我曾經看到過這樣一種情況,即在控制台應用程序中阻塞ReadByte()的SerialPort被移植到WinForms應用程序而沒有有意義的代碼更改,問題就消失了。 沒有機會進行徹底調查,但是您可以查看在WinForms下是否有更好的運氣,然后從那里進行故障排除。

這個答案有點晚了,但我想我會為下一個在這個問題上陷入困境的人而高興。

編輯:這是一個方便的WaitForBytes(count, timeout)擴展方法,可以很好地過濾掉您描述的“無限阻止”行為。

用法是: port.WaitForBytes(1)等待1個字節的數據到達。 或者為了減少開銷,請使用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) { }
  }

}

我認為當緩沖區中沒有數據時,您的循環會在第一次運行(啟動時)時中斷。

if (result < 0)
    break;

以后的循環未運行,並且您在控制台上看不到任何數據。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM