C#使用重叠I/O时出现内存损坏与System.ExecutionEngineException
解决C#串口重叠I/O中的System.ExecutionEngineException(内存损坏)问题
问题根源分析
你的代码存在几个核心问题,直接触发内存损坏异常:
- 重叠结构生命周期不匹配:栈上的
NativeOverlapped实例在异步操作未完成时可能被覆盖/回收,堆分配的overlappedPtr被错误复用,且未处理异步操作的pending状态 - 未正确处理
WaitCommEvent的异步结果:忽略ERROR_IO_PENDING的情况,直接进入下一次循环导致重叠结构被重复使用 - 异步操作未完成时提前销毁资源:取消线程时未终止pending的I/O操作,直接释放内存或关闭句柄
修正后的实现方案
核心修复点
- 每个异步I/O操作使用独立的、生命周期稳定的
NativeOverlapped结构,避免复用或提前释放 - 正确处理
ERROR_IO_PENDING,等待异步操作完成后再处理结果 - 取消时调用
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
相关产品推荐
相关产品推荐

