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

Unity中Socket.ConnectAsync成功连接后程序无限挂起问题

问题描述

原本在控制台应用中运行正常的简易TCP客户端/服务端,将客户端代码移植到Unity后,调用socket.ConnectAsync并使用.Wait()阻塞等待连接时,程序出现无限挂起。即使服务器已接受连接(服务器日志已记录),Unity编辑器仍无法恢复正常。

控制台应用中无此问题,使用阻塞式socket.Connect替代异步方法时,可实现预期的阻塞至连接成功后继续执行的行为。Unity版本为2022.3.12f1。

客户端类代码

public class TCPClient {
    private ArraySegment<byte> buffer;
    private Socket? socket;

    public delegate void onReceive_delegate(int bytesReceived, EndPoint endpoint);
    public onReceive_delegate? onReceive;

    public TCPClient(ArraySegment<byte> buffer) {
        this.buffer = buffer;
    }

    private void Open() {
        if (socket != null) Dispose();
        socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.ReuseAddress, true);
    }

    public async Task Connect(EndPoint remoteEP) {
        Open();
        await socket!.ConnectAsync(remoteEP); // 此处导致挂起
        _ = Listen();
    }

    private async Task Listen() {
        if (socket == null) return;

        int receivedBytes = await socket.ReceiveAsync(buffer, SocketFlags.None);
        onReceive?.Invoke(receivedBytes, socket.RemoteEndPoint!);

        _ = Listen();
    }

    public void Disconnect() {
        Dispose();
    }

    public void Dispose() {
        if (socket == null) return;

        socket.Dispose();
        socket = null;
    }
}

执行连接的代码

public class Test : MonoBehaviour {
    void Start() {
        byte[] buffer = new byte[1024];
        TCPClient client = new TCPClient(buffer);

        // _ = client.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 54892)); 
        // ^=> 此写法不会导致编辑器挂起,符合预期

        client.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 54892)).Wait(); 
        // ^=> 导致编辑器无限挂起
    }
}
问题原因

Unity主线程采用单线程同步上下文,当调用.Wait()阻塞主线程时,await socket.ConnectAsync完成后会尝试回到原同步上下文(即被阻塞的主线程)继续执行后续代码,但主线程此时被.Wait()卡死,无法处理异步回调,最终造成死锁,表现为程序无限挂起。

而控制台应用的同步上下文是线程池上下文,await完成后可直接在线程池线程继续执行,不会出现死锁。

解决方案

方案1:使用同步连接(简单直接)

如果必须阻塞至连接完成,直接使用阻塞式socket.Connect方法,这是Unity环境下最稳妥的同步连接方式:

public void Connect(EndPoint remoteEP) {
    Open();
    socket!.Connect(remoteEP); // 同步阻塞连接
    _ = Listen();
}

注意:同步连接会阻塞主线程,若连接超时会导致编辑器/游戏卡顿,建议添加超时逻辑(比如用Socket.Poll配合连接尝试)。

方案2:避免阻塞主线程(推荐)

放弃使用.Wait(),改用Unity支持的异步生命周期方法,将Start改为async void:

public class Test : MonoBehaviour {
    async void Start() {
        byte[] buffer = new byte[1024];
        TCPClient client = new TCPClient(buffer);
        await client.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 54892));
        // 连接成功后的逻辑在此处执行
    }
}

这种方式不会阻塞主线程,符合Unity的异步编程规范。

方案3:修改异步方法避免回到原上下文

如果一定要保留.Wait(),可在await时添加ConfigureAwait(false),让异步完成后不回到Unity主线程,直接在线程池线程执行后续代码:

public async Task Connect(EndPoint remoteEP) {
    Open();
    await socket!.ConnectAsync(remoteEP).ConfigureAwait(false); // 跳过原同步上下文
    _ = Listen();
}

注意:后续的Listen方法也会在线程池线程执行,若需要操作Unity UI或场景对象,需通过UnityMainThreadDispatcher等工具切换回主线程。

内容的提问来源于Stack Exchange,提问作者匿名

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 15:47:22