【问题标题】:Serial Port Receive Thread Not Behaving as Expected - C++串行端口接收线程未按预期运行 - C++
【发布时间】:2010-12-08 22:56:50
【问题描述】:

我的应用程序使用一个单独的线程来异步处理接收到的串行数据。 PC 按预期进入接收处理程序,但从那里开始变得很奇怪。

这是我的线程函数:

// Create event for OVERLAPPED structure.
s_ov.hEvent = ::CreateEvent(
    NULL,                           // No security
    TRUE,                           // Create a manual-reset event object
    FALSE,                          // Initial state is non-signaled
    NULL                            // No name specified
    );

// Load event handles.
pHandles[0] = s_hSerialPortRxThreadExitEvent;

while ( bContinue )
{
    if ( !::WaitCommEvent( s_hSerialPort, &dwEventMask, &s_ov ) )
    {
        if ( ::GetLastError() != ERROR_IO_PENDING )
        {
            TRACE(_T("SerialPortRxThreadFn : Call to WaitCommEvent failed.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), ::GetLastError(), __WFILE__, __LINE__);
            return ::GetLastError();
        }
    }

    pHandles[1] = s_ov.hEvent;

    dwObjectWaitState = ::WaitForMultipleObjects( 2, pHandles, FALSE, INFINITE );

    switch ( dwObjectWaitState )
    {
    case WAIT_ABANDONED:
        TRACE(_T("SerialPortRxThreadFn : Owner thread terminated prematurely.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), ERROR_ARENA_TRASHED, __WFILE__, __LINE__);
        return ERROR_ARENA_TRASHED;
        break;

    case WAIT_TIMEOUT:
        TRACE(_T("SerialPortRxThreadFn : The timeout is set to INFINITE; there should be no timeout.  State is nonsignaled.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), WAIT_TIMEOUT, __WFILE__, __LINE__);
        return WAIT_TIMEOUT;
        break;

    case WAIT_FAILED:
        TRACE(_T("SerialPortRxThreadFn : Call to WaitCommEvent failed.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), ::GetLastError(), __WFILE__, __LINE__);
        return ::GetLastError();
        break;

    case WAIT_OBJECT_0:             // thread exit event signalled
        bContinue = FALSE;

        if ( !::ResetEvent( pHandles[0] ) )
        {
            TRACE(_T("SerialPortRxThreadFn  : Failed to reset the serial port thread exit event.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), ::GetLastError(), __WFILE__, __LINE__);
            return ::GetLastError();
        }
        break;

    case WAIT_OBJECT_0 + 1:         // OVERLAPPED structure event signalled
        // Read data from serial port.
        if ( !::ReadFile( s_hSerialPort, pBuf, RX_BUF_SIZE, &dwWritten, &s_ov ) ) // <- Set breakpoint here
        {
            TRACE(_T("SerialPortRxThreadFn : Call to ReadFile filed.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), ::GetLastError(), __WFILE__, __LINE__);
            return ::GetLastError();
        }

        // Discontinue thread operation if there are no more bytes in the serial port receive buffer.
        if ( dwWritten == 0 ) // <- Or, set breakpoint here
        {
            bContinue = FALSE;
        }
        // Copy the received bytes to the thread-safe buffer.
        else if ( !s_pobjRxRingBuffer->Add( pBuf, dwWritten, TRUE ) )
        {
            TRACE(_T("SerialPortRxThreadFn : Failed to add bytes to ring buffer.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), ERROR_INSUFFICIENT_BUFFER, __WFILE__, __LINE__);
            return ERROR_INSUFFICIENT_BUFFER;
        }
        else if ( s_SpCallbackFn != NULL )
        {
            // Notify application of received data.
            if ( (dwRetVal = s_SpCallbackFn( s_pobjRxRingBuffer->ItemsInBuffer() )) != ERROR_SUCCESS )
            {
                TRACE(_T("SerialPortRxThreadFn : Serial port callback function failed.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwRetVal, __WFILE__, __LINE__);
                return dwRetVal;
            }
        }

        if ( !::ResetEvent( pHandles[1] ) )
        {
            TRACE(_T("SerialPortRxThreadFn : Failed to reset the OVERLAPPED structure event.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), ::GetLastError(), __WFILE__, __LINE__);
            return ::GetLastError();
        }
        break;

    default:
        // Do nothing.
        break;
    }
}

::CloseHandle( s_ov.hEvent );

return ERROR_SUCCESS;

如果我在调用ReadFile 的行上设置断点,一切都会按我的预期工作,并且PC 会进入回调函数。但是,如果我在下一行设置断点,其中dwWritten 被评估为零,它为零,表达式评估为 TRUE,并且循环退出; PC 永远不会收到回调。我究竟做错了什么?谢谢。

【问题讨论】:

  • 看起来你做的工作比你需要的多得多。为什么要在线程上异步处理串行数据?为什么不让线程阻塞在 I/O 上?
  • 此外,您正在泄漏句柄,如果不关闭句柄,您可能无法从函数返回,但您是在错误情况下这样做的。
  • 您必须在调用 ReadFile 之前重置事件 pHandles[1]。
  • @Luis:不正确,来自文档:“ReadFile 在开始 I/O 操作时将事件重置为非信号状态。”
  • 是的,你是对的,对不起我的错误。

标签: c++ multithreading visual-studio-2008 serial-communication


【解决方案1】:

我不是 Win32 API 方面的专家,但这听起来确实像是时间问题(这是heisenbugs 的常见原因。)假设当您到达ReadFile 时,没有数据可供读取.闯入调试器可能会为其提供足够的暂停以等待数据到达,因此当您恢复/跳过ReadFile 时,它会成功。

除了数据的到达之外,还有很多事情可以触发事件。你可能想检查一下你的dwEventMask,看看我的假设是否正确。

【讨论】:

  • 你说的都是真的,但是这个建议很糟糕。使用dwEventMask 是查明是否已收到数据的错误方法。正确的方法是调用SetCommTimeoutsReadFile,这样一旦数据进入内核接收缓冲区,读取就会完成。 WaitCommEvent 告诉您其他信息,例如流控制引脚的状态。
  • @Ben 也许我不清楚。我并不是说处理应该基于dwEventMask。我的意思是应该检查dwEventMask 以确定我的假设是否正确。
  • 在启动线程之前,EV_RXCHAR 是我在调用SetCommMask 时设置的唯一标志。 WaitCommEvent 是否还有其他标志可以检索?我认为它只会检索掩码中设置的标志。
  • @Jim TBH,我不是这个 API 的专家。我会把它留给比我更有知识的人来回答你的问题。
  • 我似乎已经通过添加临界区锁并将 if-else 阶梯更改为一系列 if 语句来解决问题。你对 heisenbugs 的建议可能最接近我的经历,所以我接受你的回答。谢谢。
【解决方案2】:

看这段代码很痛苦,写了一些。它的冗长最好停留在其他人的类库中。几个红旗。您假设 WaitCommEvent() 完成意味着您可以调用 ReadFile()。通常情况下,您使用的事件掩码是不可见的,但是串行端口想要告诉您的还有很多其他原因。另一个问题是 WaitCommEvent 可能会立即完成。这并不罕见,接收缓冲区中有可用的东西。

从某处窃取此代码,这是硬代码。已经完成了。

【讨论】:

  • 在启动线程之前,EV_RXCHAR 是我在调用SetCommMask 时设置的唯一标志。不幸的是,在这种情况下,从公司外部借用代码不是一种选择。我可以看示例等,但所有代码都必须由我自己或公司的其他人编写。
【解决方案3】:

WaiCommEvent 的文档指出,在您使用了等待函数(如 WaitForMultipleEObjects(...))后,您可以使用 GetOverlappedResult(...) 函数来获取操作结果。应该不需要 Read\Write-File(...)。

【讨论】:

  • 如果不调用ReadFile,您将无法获取接收到的字节。事实上,需要的是 ReadFile 而不是 WaitCommEvent
  • @Ben Voigt:我认为你错了。如果您阅读有关 WaitCommEvent 的文档,您可以阅读到 EventMask 参数有一个名为 EV_RXCHAR 的标志。 ReadFile 是选择而不是需要。
  • 我以前用过这些功能。相信我,您可以使用ReadFile(或ReadFileEx,我更喜欢)以重叠模式阅读,而根本不用WaitCommEventReadFile 在其 OVERLAPPED 参数中使用内核事件,您可以等待。 WaitCommEvent 可能会告诉您串行端口何时有活动,但它不会为您提供实际数据。
  • @Ben Voigt:我都知道。多年来,我在遗留 DOS 代码和 Windows-API 中开发了 USB-、Modem-、SerialPort 和 Parallelport 驱动程序和应用程序。在 Windows 下,除了 Read- 和 WriteFile(-Ex) 以及偶尔使用的 DeviceIoControl 之外,我从未使用过其他任何东西。
【解决方案4】:

您不需要通信事件来异步读取数据。只需调用ReadFile,您将收到“错误”ERROR_IO_PENDING,当数据到达时,将发出事件信号,然后您可以获得字节数GetOverlappedResult,数据将在您最初提供的缓冲区中到ReadFile

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-10
    • 2018-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-17
    相关资源
    最近更新 更多