阅读从单一的SerialPort字节同步? [英] Reading single bytes from SerialPort synchronously?

查看:189
本文介绍了阅读从单一的SerialPort字节同步?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图从一个串口读取一个字节的时间。我有以下code在我的控制台应用程序:

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).

然而,在ReadByte通话并没有什么只有几个街区发生。

However, it just blocks on the ReadByte call and nothing happens.

我知道,我的串行设备是否正常工作:如果我使用万亿期限,我看到的数据。如果我使用 DataReceived 事件,并调用 SerialPort.ReadExisting ,那么我可以看到的数据。

I know that my serial device is working: if I use Tera Term, I see the data. If I use the DataReceived event, and call SerialPort.ReadExisting, then I can see the data.

不过,我不打扰约性能(至少目前还没有),以及协议我实施工作更好地当处理同步进行。

However, I'm not bothered about performance (at least, not yet), and the protocol I'm implementing works better when dealt with synchronously.

所以:我究竟做错了什么?为什么没有 ReadByte 返回?

So: what am I doing wrong? Why doesn't ReadByte return?

推荐答案

您可以通过做这样的事情,并调用 WaitForData()前使异步行为​​看同步每次读:

You could make the asynchronous behavior look synchronous by doing something like this, and calling WaitForData() before each read:

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.

我已经看到了一些奇怪的事情SerialPort类,包括您所描述的行为。请记住,在DataReceived事件检索被调用的辅助线程(见的 MSDN )。您可以使用锁()与Monitor.Wait()和.Pulse(语义)获得稍好的性能,如<一个href="http://stackoverflow.com/questions/1355398/monitor-vs-waithandle-based-thread-sync/1355415#1355415">here

I've seen some strange things with the SerialPort class, including the behavior you described. Keep in mind that the DataReceived event gets called on a secondary thread (see MSDN). You can get slightly better performance using lock() semantics with Monitor.Wait() and .Pulse(),as described here

如果你懒,你也可以试试您的来电ReadByte前右侧插入Thread.sleep()方法行(如200毫秒),看它是否有差别。此外,我可以发誓我曾经看到的地方被阻塞ReadByte一个的SerialPort()在一个控制台应用程序移植到WinForms应用程序没有意义的code的变化和问题走了的情况。没有机会彻底调查,但你可以看到,如果你有以下的WinForms任何更好的运气,然后从那里解决。

If you're lazy, you could also try inserting a Thread.Sleep() line (e.g. 200ms) right before your call to ReadByte to see if it makes a difference. 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. 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.

这答案是有点晚了,但我想我会附和下一个人谁得到难倒了这个问题。

This answer is a little late, but I figured I'd chime in for the next person who gets stumped on this issue.

编辑:下面是一个方便的 WaitForBytes(计数,超时)扩展,它确实过滤掉了无限堵的好方法行为你描述的。

Here's a handy WaitForBytes(count, timeout) extension method that does a good job of filtering out the "infinite blocking" behavior you described.

用法是: port.WaitForBytes(1)来等待数据到达的1个字节。或者更少的开销,使用 SerialPortWatcher.WaitForBytes(N)代替。

Usage is: port.WaitForBytes(1) to wait for 1 byte of data to arrive. Or for less overhead, use SerialPortWatcher.WaitForBytes(n) instead.

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:
    // http://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) { }
  }

}

这篇关于阅读从单一的SerialPort字节同步?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆