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

Unity中如何用C# async Task后台执行无返回的耗时Socket发送操作

Unity中异步发送截图的高效async/await实现方案

当前代码分析

你现在的写法已经能将发送操作丢到后台线程,不阻塞主线程,但存在两个需要优化的点:

  1. 未处理异常:如果socket.TrySendFrame抛出异常,未被观察的Task异常会触发Unity日志报错,甚至在旧版.NET中导致进程崩溃;
  2. Task.Run的开销:如果你的Socket封装支持原生异步API,用Task.Run包装同步方法属于"伪异步",会占用线程池线程,不如原生IO操作高效。

优化方案一:保留Task.Run并处理异常(适合无原生异步API的Socket)

先修正协程的返回值(Unity协程必须返回IEnumerator,而非void),同时在异步方法内部捕获异常,避免未观察异常:

// 异步发送方法,内部处理异常
private static async Task SendScreenshotAsync(PushSocket socket, byte[] frame)
{
    try
    {
        // 将同步发送逻辑丢到线程池线程,不阻塞主线程
        await Task.Run(() => socket.TrySendFrame(frame));
    }
    catch (Exception ex)
    {
        // 记录发送异常,便于调试
        Debug.LogError($"截图发送失败:{ex.Message}\n{ex.StackTrace}");
    }
}

// 主线程协程,负责捕获截图并触发后台发送
private IEnumerator ScreenshotCoroutine()
{
    while (isRunning)
    {
        yield return new WaitForSeconds(1f);
        
        // 主线程捕获截图(符合Unity线程安全要求)
        byte[] currentFrame = CaptureScreenshot(); // 替换为你的截图逻辑
        
        // 触发后台发送,不等待结果,不阻塞主线程
        _ = SendScreenshotAsync(screenSocketWide, currentFrame);
        
        // 重置并准备下一帧
        // [...]
    }
}

调用协程时,在合适的地方(比如MonoBehaviour的Start方法)执行:

private void Start()
{
    StartCoroutine(ScreenshotCoroutine());
}

优化方案二:使用Socket原生异步API(更高效,推荐)

如果你的PushSocket封装了原生异步发送方法(比如TrySendFrameAsync),直接使用async/await调用即可,无需Task.Run。这种方式基于IO完成端口(IOCP),不占用线程池线程等待IO操作,资源利用率更高:

// 利用原生异步API发送,无额外线程开销
private static async Task SendScreenshotAsync(PushSocket socket, byte[] frame)
{
    try
    {
        await socket.TrySendFrameAsync(frame);
    }
    catch (Exception ex)
    {
        Debug.LogError($"截图发送失败:{ex.Message}\n{ex.StackTrace}");
    }
}

// 协程调用方式与方案一完全一致
private IEnumerator ScreenshotCoroutine()
{
    while (isRunning)
    {
        yield return new WaitForSeconds(1f);
        
        byte[] currentFrame = CaptureScreenshot();
        
        _ = SendScreenshotAsync(screenSocketWide, currentFrame);
        
        // [...]
    }
}

关键知识点澄清

  • 你提到"不使用await无法真正并行执行"是误解:Task.Run本身就会将逻辑调度到线程池线程并行执行,await只是用来等待执行完成并释放当前线程。你的需求是无需等待结果,所以可以用_ = Task的方式直接触发异步操作;
  • 直接创建Thread的缺点:每次循环新建Thread会带来巨大的线程创建销毁开销,而线程池(Task.Run)会复用线程,原生异步API则完全不占用等待线程,效率远高于手动创建Thread;
  • Unity线程规则:后台线程绝对不能调用Unity的GameObject/Component等API,你的Socket发送操作不涉及Unity API,所以完全安全。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 03:26:06