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

将Pcap.Net的ReceivePackets封装进线程/任务等引发访问冲突异常

如何将Pcap.Net的ReceivePackets方法封装到线程/Task/BackgroundWorker中?

我尝试用Thread/Task/BackgroundWorker封装Pcap.Net库的ReceivePackets方法,但抛出了System.AccessViolationException: 尝试读取或写入受保护的内存错误,以下是我的代码:

线程创建代码

PacketDevice selectedDevice = allDevices[deviceIndex - 1];

// Open the device
using (communicator =
    selectedDevice.Open(65536,                                  // 捕获的数据包部分长度
                                                                // 65536确保在所有链路层都能捕获完整数据包
                        PacketDeviceOpenAttributes.Promiscuous, // 混杂模式
                        1000))                                  // 读取超时时间
{
    Console.WriteLine("Listening on " + selectedDevice.Description + "...");

    // 启动捕获线程
    pThread.Start();

}

recvP函数与数据包处理函数

private void recvP()
{
    communicator.ReceivePackets(0, PacketHandler);
}

// Pcap.Net收到每个数据包时调用的回调函数
private void PacketHandler(Packet packet)
{
    UdpDatagram datagram = packet.Ethernet.IpV4.Udp;
    if (datagram != null && datagram.SourcePort == 6969)
    {
        MemoryStream stream = datagram.Payload.ToMemoryStream();
        byte[] byteStream = stream.ToArray();

        current_packet_id = BitConverter.ToUInt16(byteStream, 0);
        Console.WriteLine("Got chunk number: " + current_packet_id);
        if (current_packet_id < last_packet_id) // 新图像(图像的第一个数据包)
        {
            if (!firstImage)
                ShowImage(); // 如果前一张图像的所有数据包都已收到,显示图像

            data.Clear(); // 清空之前图像的所有数据
            data.AddRange(byteStream.Skip(2).Take(byteStream.Length - 2).ToList());
        }
        else // 同一张图像的后续数据包
            data.AddRange(byteStream.Skip(2).Take(byteStream.Length - 2).ToList());

        last_packet_id = current_packet_id;
    }
}

问题原因

错误核心是communicator对象的生命周期和线程执行不同步:using块会在代码执行完毕后自动释放communicator,但你在using块内启动线程后,线程还在后台执行ReceivePackets,此时communicator已经被销毁,线程尝试访问已释放的内存就会触发AccessViolationException。

正确的封装方式

1. 使用Thread(手动管理对象生命周期)

把using块替换为手动释放逻辑,确保communicator在线程执行完毕后再销毁:

PacketDevice selectedDevice = allDevices[deviceIndex - 1];
// 不用using,手动控制资源释放时机
communicator = selectedDevice.Open(65536, PacketDeviceOpenAttributes.Promiscuous, 1000);
try
{
    Console.WriteLine("Listening on " + selectedDevice.Description + "...");
    pThread = new Thread(recvP);
    pThread.Start();
    pThread.Join(); // 等待线程执行完毕,再执行资源释放
}
finally
{
    // 确保无论线程是否异常,都释放资源
    communicator?.Dispose();
}

2. 使用Task异步处理

利用await等待任务完成,保证using块在任务结束后才释放communicator:

async Task StartCaptureAsync()
{
    PacketDevice selectedDevice = allDevices[deviceIndex - 1];
    using (var communicator = selectedDevice.Open(65536, PacketDeviceOpenAttributes.Promiscuous, 1000))
    {
        Console.WriteLine("Listening on " + selectedDevice.Description + "...");
        // 在Task中执行捕获逻辑,await确保任务完成后再退出using块
        await Task.Run(() => communicator.ReceivePackets(0, PacketHandler));
    }
}

3. 使用BackgroundWorker(WinForms/WPF场景)

在后台线程的DoWork事件中完整处理捕获逻辑,确保communicator在事件执行期间有效:

private void StartCaptureWithBackgroundWorker()
{
    PacketDevice selectedDevice = allDevices[deviceIndex - 1];
    var worker = new BackgroundWorker();
    
    worker.DoWork += (sender, e) =>
    {
        // 在后台线程内创建并使用communicator,生命周期覆盖整个捕获过程
        using (var communicator = selectedDevice.Open(65536, PacketDeviceOpenAttributes.Promiscuous, 1000))
        {
            communicator.ReceivePackets(0, PacketHandler);
        }
    };

    worker.RunWorkerCompleted += (sender, e) =>
    {
        // 捕获结束后的清理或通知逻辑
        Console.WriteLine("Capture finished.");
    };

    worker.RunWorkerAsync();
}

额外注意事项

  • 线程安全:data、current_packet_id、last_packet_id是多线程共享变量,必须加锁避免竞态条件:
private readonly object _lock = new object();

private void PacketHandler(Packet packet)
{
    UdpDatagram datagram = packet.Ethernet.IpV4.Udp;
    if (datagram != null && datagram.SourcePort == 6969)
    {
        MemoryStream stream = datagram.Payload.ToMemoryStream();
        byte[] byteStream = stream.ToArray();

        lock(_lock) // 加锁保护共享变量
        {
            current_packet_id = BitConverter.ToUInt16(byteStream, 0);
            Console.WriteLine("Got chunk number: " + current_packet_id);
            if (current_packet_id < last_packet_id)
            {
                if (!firstImage)
                    ShowImage();

                data.Clear();
                data.AddRange(byteStream.Skip(2).Take(byteStream.Length - 2).ToList());
            }
            else
                data.AddRange(byteStream.Skip(2).Take(byteStream.Length - 2).ToList());

            last_packet_id = current_packet_id;
        }
    }
}
  • UI线程更新:如果ShowImage是更新UI的操作,WinForms中需要用Invoke切换到UI线程,WPF用Dispatcher.Invoke,否则会触发跨线程访问UI的错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 21:15:34