You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#使用重叠I/O时出现内存损坏与System.ExecutionEngineException

解决C#串口重叠I/O中的System.ExecutionEngineException(内存损坏)问题

问题根源分析

你的代码存在几个核心问题,直接触发内存损坏异常:

  • 重叠结构生命周期不匹配:栈上的NativeOverlapped实例在异步操作未完成时可能被覆盖/回收,堆分配的overlappedPtr被错误复用,且未处理异步操作的pending状态
  • 未正确处理WaitCommEvent的异步结果:忽略ERROR_IO_PENDING的情况,直接进入下一次循环导致重叠结构被重复使用
  • 异步操作未完成时提前销毁资源:取消线程时未终止pending的I/O操作,直接释放内存或关闭句柄

修正后的实现方案

核心修复点

  1. 每个异步I/O操作使用独立的、生命周期稳定的NativeOverlapped结构,避免复用或提前释放
  2. 正确处理ERROR_IO_PENDING,等待异步操作完成后再处理结果
  3. 取消时调用CancelIoEx终止所有pending操作,再安全清理资源

完整修正代码

private void ReadThread(CancellationToken token)
{
    IntPtr portHandle = _portHandle;
    SetupComPort(portHandle);

    var buffer = new byte[16 * 1024];
    uint eventMask = 0;

    // 注册取消回调,终止所有pending的I/O操作
    using var cancellationRegistration = token.Register(() =>
    {
        CancelIoEx(portHandle, IntPtr.Zero);
    });

    while (!token.IsCancellationRequested)
    {
        using var waitEvent = new ManualResetEvent(false);
        var overlapped = new NativeOverlapped
        {
            OffsetLow = 0,
            OffsetHigh = 0,
            EventHandle = waitEvent.SafeWaitHandle.DangerousGetHandle()
        };

        // 为当前WaitCommEvent操作分配独立的重叠结构内存
        IntPtr overlappedPtr = Marshal.AllocHGlobal(Marshal.SizeOf<NativeOverlapped>());
        try
        {
            Marshal.StructureToPtr(overlapped, overlappedPtr, false);

            bool waitResult = WaitCommEvent(portHandle, ref eventMask, overlappedPtr);
            uint lastError = (uint)Marshal.GetLastWin32Error();

            // 处理非pending的错误
            if (!waitResult && lastError != ERROR_IO_PENDING)
            {
                Thread.Sleep(100);
                continue;
            }

            // 等待异步操作完成或被取消
            if (WaitForSingleObject(waitEvent.SafeWaitHandle.DangerousGetHandle(), INFINITE) == WAIT_OBJECT_0)
            {
                // 获取WaitCommEvent的完成状态
                if (!GetOverlappedResult(portHandle, overlappedPtr, out _, false))
                {
                    continue;
                }

                if ((eventMask & EVRXCHAR) != 0)
                {
                    // 为ReadFile操作创建独立的重叠结构
                    using var readEvent = new ManualResetEvent(false);
                    var readOverlapped = new NativeOverlapped
                    {
                        OffsetLow = 0,
                        OffsetHigh = 0,
                        EventHandle = readEvent.SafeWaitHandle.DangerousGetHandle()
                    };

                    IntPtr readOverlappedPtr = Marshal.AllocHGlobal(Marshal.SizeOf<NativeOverlapped>());
                    try
                    {
                        Marshal.StructureToPtr(readOverlapped, readOverlappedPtr, false);

                        bool readResult = ReadFile(portHandle, buffer, (uint)buffer.Length, out uint bytesRead, readOverlappedPtr);
                        uint readError = (uint)Marshal.GetLastWin32Error();

                        if (!readResult && readError != ERROR_IO_PENDING)
                        {
                            continue;
                        }

                        if (WaitForSingleObject(readEvent.SafeWaitHandle.DangerousGetHandle(), INFINITE) == WAIT_OBJECT_0)
                        {
                            GetOverlappedResult(portHandle, readOverlappedPtr, out bytesRead, false);
                            // 处理读取到的数据
                            ProcessReceivedData(buffer, (int)bytesRead);
                        }
                    }
                    finally
                    {
                        if (readOverlappedPtr != IntPtr.Zero)
                        {
                            Marshal.FreeHGlobal(readOverlappedPtr);
                        }
                    }
                }
            }
        }
        finally
        {
            if (overlappedPtr != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(overlappedPtr);
            }
        }
    }
}

// 数据处理辅助方法
private void ProcessReceivedData(byte[] buffer, int length)
{
    // 替换为你的业务逻辑
}

关键注意事项

  • 独立分配重叠结构:WaitCommEvent和ReadFile是两个独立的异步操作,必须使用各自的重叠结构,禁止复用
  • 等待操作完成再清理:通过WaitForSingleObject等待事件信号,确保异步操作完全结束后再释放重叠结构内存
  • 取消时终止pending操作:调用CancelIoEx确保所有未完成的I/O操作被终止,避免内存泄漏或损坏
  • 避免栈上重叠结构:栈内存可能被线程调度覆盖,异步操作必须使用堆分配的NativeOverlapped结构

内容的提问来源于stack exchange,提问作者Inas Hamad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 00:02:14