[英]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.