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

C#线程中接收HoloLens帧的最优方案及性能瓶颈解决

HoloLens帧传输帧率骤降问题解决

问题现象

通过单Socket连接向服务器传输HoloLens捕获的视频帧时:

  • 初始使用Task.Run发起发送任务,前期帧率正常,后续逐渐降至0,调试发现线程数量过载(大量线程堆积导致系统资源耗尽)。
  • 改用Parallel.Invoke后帧率仍偏低,发送任务的处理会干扰新帧的接收。
  • 尝试async/await结合取消令牌方案,问题未得到缓解。

相关代码

C# 帧到达回调函数

private void ColorFrameReader_FrameArrivedAsync(MediaFrameReader sender, MediaFrameArrivedEventArgs args)
{
    var frame = sender.TryAcquireLatestFrame();
    if (frame != null)
    {
        SoftwareBitmap originalBitmap = null;
        var inputBitmap = frame.VideoMediaFrame?.SoftwareBitmap;
        if (inputBitmap != null)
        {
            // 转换为XAML控件支持的BGRA8预乘alpha格式
            originalBitmap = SoftwareBitmap.Convert(inputBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);                 
            SoftwareBitmap outputBitmap = new SoftwareBitmap(BitmapPixelFormat.Bgra8, originalBitmap.PixelWidth, originalBitmap.PixelHeight, BitmapAlphaMode.Premultiplied);

            // 调用发送逻辑
            ab_func(originalBitmap);
        }

        Interlocked.Increment(ref _frameCount);
    }
}

async void ab_func(SoftwareBitmap fr)
{
   await Task.Run(() => { _helper.Connect(fr); });
}

C++ Socket发送方法

void OpenCVHelper::Connect(SoftwareBitmap^ input)
{
  Mat inputMat, outputMat;
  if (!(TryConvert(input)))
  {
      return;
  }
int imgSize = GlobalMat.total() * GlobalMat.elemSize();

auto t0 = std::chrono::system_clock::now();
auto nanosec = t0.time_since_epoch();

if (ConnectSocket == INVALID_SOCKET) {
    WSACleanup();
    return;
}
uchar* iptr = GlobalMat.data;
t0 = std::chrono::system_clock::now();
nanosec = t0.time_since_epoch();
m_OutStream << "timestamp before sending of frame " << i++ << " = " << std::chrono::duration_cast<std::chrono::milliseconds>(nanosec).count() << std::endl;

// 发送帧到服务器
if ((bytes = send(ConnectSocket, (char*)iptr, imgSize, 0)) < 0) {
    m_OutStream << "problem 2 ! \n";
}

return;
}

问题根源分析

  1. 线程池过载:每次帧到达就通过Task.Run/Parallel.Invoke创建新任务,导致线程池线程数量激增,线程上下文切换开销剧增,最终拖垮帧率。
  2. 无并发控制:没有限制发送任务的并发数量,当Socket发送速度跟不上帧生成速度时,任务会持续积压,占用大量系统资源。
  3. 同步Socket阻塞:send是同步阻塞调用,每个任务都会占用线程直到发送完成,进一步加剧线程资源消耗。

解决方案

1. 引入有界队列+固定线程处理

用阻塞队列缓存待发送的帧,仅用固定数量的线程处理发送逻辑,避免线程爆炸:

// 初始化有界阻塞队列,限制最大缓存帧数量(防止内存溢出)
private readonly BlockingCollection<SoftwareBitmap> _frameQueue = new BlockingCollection<SoftwareBitmap>(boundedCapacity: 10);

// 应用启动时启动固定发送线程
public void StartSenderThreads()
{
    // 启动1个发送线程,可根据网络情况调整为2
    Task.Run(() =>
    {
        foreach (var frame in _frameQueue.GetConsumingEnumerable())
        {
            _helper.Connect(frame);
            frame.Dispose(); // 及时释放帧资源
        }
    });
}

// 修改帧到达回调,仅将帧加入队列
private void ColorFrameReader_FrameArrivedAsync(MediaFrameReader sender, MediaFrameArrivedEventArgs args)
{
    var frame = sender.TryAcquireLatestFrame();
    if (frame != null)
    {
        SoftwareBitmap originalBitmap = null;
        var inputBitmap = frame.VideoMediaFrame?.SoftwareBitmap;
        if (inputBitmap != null)
        {
            originalBitmap = SoftwareBitmap.Convert(inputBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
            // 队列满时丢弃旧帧,避免阻塞帧捕获线程
            if (!_frameQueue.TryAdd(originalBitmap))
            {
                originalBitmap.Dispose();
            }
        }
        Interlocked.Increment(ref _frameCount);
        frame.Dispose(); // 释放原生帧资源
    }
}

2. 替换同步Socket为异步发送

修改C++发送逻辑,使用异步WSASend避免线程阻塞:

void OpenCVHelper::AsyncConnect(SoftwareBitmap^ input)
{
    Mat inputMat, outputMat;
    if (!(TryConvert(input)))
    {
        input->Dispose();
        return;
    }
    int imgSize = GlobalMat.total() * GlobalMat.elemSize();
    uchar* iptr = GlobalMat.data;

    if (ConnectSocket == INVALID_SOCKET) {
        WSACleanup();
        input->Dispose();
        return;
    }

    // 异步发送配置
    WSABUF buf;
    buf.buf = (char*)iptr;
    buf.len = imgSize;
    DWORD bytesSent;
    DWORD flags = 0;
    OVERLAPPED overlapped = {0};
    overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

    if (WSASend(ConnectSocket, &buf, 1, &bytesSent, flags, &overlapped, NULL) == SOCKET_ERROR)
    {
        if (WSAGetLastError() != WSA_IO_PENDING)
        {
            m_OutStream << "Async send failed! \n";
            CloseHandle(overlapped.hEvent);
            input->Dispose();
            return;
        }
    }

    // 等待异步发送完成(实际场景可改用回调避免阻塞)
    GetOverlappedResult(ConnectSocket, &overlapped, &bytesSent, TRUE);
    CloseHandle(overlapped.hEvent);
    input->Dispose();
}

3. 强化帧资源管理

  • 在帧到达回调中及时调用frame.Dispose()释放原生资源,避免内存泄漏。
  • 队列中的帧处理完成后立即释放,减少内存占用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 20:36:18