2009-06-01 43 views
0

我試圖從串口讀取一次一個字節。我有下面的代碼在我的控制檯應用程序:同步讀取SerialPort中的單個字節?

// 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爲什麼不返回?

+0

這是在WinForms,控制檯還是SmartDevice上? – 2009-06-01 09:46:25

+0

控制檯應用程序(但也許稍後WinForms);絕對不是Compact Framework。 – 2009-06-01 09:49:27

回答

3

你可以做這樣的事情,每個收到主叫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()的lock()語義獲得稍好的性能,如上所述here

如果您懶惰,還可以嘗試插入Thread.Sleep()行在您致電ReadByte之前查看它是否有所作爲。此外,我可以發誓我曾經看到一個案例,一個在控制檯應用程序中阻止ReadByte()的串行端口被移植到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) { } 
    } 

} 
1

我覺得你的循環在第一次運行時(開始時),當緩衝區中沒有數據的時候會中斷。

if (result < 0) 
    break; 

稍後循環未運行,並且您在控制檯上看不到任何數據。