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

WPF客户端连接服务器并调用主窗口方法时崩溃求助

WPF客户端TCP事件触发UI操作崩溃问题

我基于TCP搭建了简易服务器和ClientHandle脚本,服务器启动10秒后会调用AddDevice方法,此时触发OnAddedDevice事件调用WPF主窗口的GoToPage方法时,客户端直接崩溃。我尝试过直接调用方法、重新实例化对象、修改数据包信息等操作,问题仍未解决。

相关代码

ClientHandle类

public class ClientHandle
{
    public static event Action OnAddedDevice;
    
    public static void Welcome(Packet packet)
    {
        string msg = packet.ReadString();
        int myId = packet.ReadInt();
        
        Client.instance.myId = myId;
        ClientSend.WeclomeReceived();
    }

    public static void AddDevice(Packet packet)
    {
        int myId = packet.ReadInt();
        
        OnAddedDevice?.Invoke();
    }
}

事件订阅代码(NetLogic类)

public NetLogic(MainWindow mainWindow)
{
    _mainWindow = mainWindow;
    ClientHandle.OnAddedDevice += TestA;
    StartServer();
    Client client = new Client();
    client.ConnectToServer();
}

Client类完整实现

public class Client
{
    public static Client instance;
    public static int dataBufferSize = 4096;

    public string ip = "127.0.0.1";
    public int port = 26950;
    public int myId = 0;
    public TCP tcp;

    private delegate void PacketHandler(Packet _packet);
    private static Dictionary<int, PacketHandler> packetHandlers;

    public Client()
    {
        instance = this;
        tcp = new TCP();
    }

    public void ConnectToServer()
    {
        InitializeClientData();
        tcp.Connect();
    }

    public class TCP
    {
        public TcpClient socket;

        private NetworkStream stream;
        private Packet receivedData;
        private byte[] receiveBuffer;

        public void Connect()
        {
            socket = new TcpClient
            {
                ReceiveBufferSize = dataBufferSize,
                SendBufferSize = dataBufferSize
            };

            receiveBuffer = new byte[dataBufferSize];
            socket.BeginConnect(instance.ip, instance.port, ConnectCallback, socket);
        }

        private void ConnectCallback(IAsyncResult _result)
        {
            socket.EndConnect(_result);

            if (!socket.Connected)
            {
                return;
            }

            stream = socket.GetStream();

            receivedData = new Packet();

            stream.BeginRead(receiveBuffer, 0, dataBufferSize, ReceiveCallback, null);
        }

        public void SendData(Packet _packet)
        {
            try
            {
                if (socket != null)
                {
                    stream.BeginWrite(_packet.ToArray(), 0, _packet.Length(), null, null);
                }
            }
            catch (Exception exception)
            {
                Console.WriteLine($"Error sending data to server via TCP: {exception}");
            }
        }

        private void ReceiveCallback(IAsyncResult _result)
        {
            try
            {
                int _byteLength = stream.EndRead(_result);
                if (_byteLength <= 0)
                {
                    // TODO: disconnect
                    return;
                }

                byte[] _data = new byte[_byteLength];
                Array.Copy(receiveBuffer, _data, _byteLength);

                receivedData.Reset(HandleData(_data));
                stream.BeginRead(receiveBuffer, 0, dataBufferSize, ReceiveCallback, null);
            }
            catch
            {
                // TODO: disconnect
            }
        }

        private bool HandleData(byte[] _data)
        {
            int _packetLength = 0;
            receivedData.SetBytes(_data);

            if (receivedData.UnreadLength() >= 4)
            {
                _packetLength = receivedData.ReadInt();
                if (_packetLength <= 0)
                {
                    return true;
                }
            }

            while (_packetLength > 0 && _packetLength <= receivedData.UnreadLength())
            {
                byte[] _packetBytes = receivedData.ReadBytes(_packetLength);
                ThreadManager.ExecuteOnMainThread(() =>
                {
                    using (Packet _packet = new Packet(_packetBytes))
                    {
                        int _packetId = _packet.ReadInt();
                        packetHandlers[_packetId](_packet);
                    }
                });

                _packetLength = 0;
                if (receivedData.UnreadLength() >= 4)
                {
                    _packetLength = receivedData.ReadInt();
                    if (_packetLength <= 0)
                    {
                        return true;
                    }
                }
            }

            if (_packetLength <= 1)
            {
                return true;
            }

            return false;
        }
    }
    
    private void InitializeClientData()
    {
        packetHandlers = new Dictionary<int, PacketHandler>()
        {
            { (int)ServerPackets.welcome, ClientHandle.Welcome },
            { (int)ServerPackets.AddDevice, ClientHandle.AddDevice },
        };
    }
}

TestA方法

public void TestA()
{
    var mainWindow = Application.Current.Windows.OfType<MainWindow>().FirstOrDefault();
    mainWindow.GoToPage();
}

问题分析与解决方案

崩溃的核心原因是WPF UI元素只能由创建它们的主线程访问,即使你通过ThreadManager.ExecuteOnMainThread处理数据包,仍可能存在以下问题:

  1. TestA中通过Application.Current.Windows查找主窗口的方式不可靠,可能返回null导致空引用异常
  2. 若ThreadManager.ExecuteOnMainThread的实现未正确调度到WPF主线程,会导致事件触发时在非UI线程执行UI操作

修复步骤:

  1. 直接使用已持有的主窗口实例,避免动态查找的不确定性:

    // 在NetLogic类中保留已注入的_mainWindow引用
    private readonly MainWindow _mainWindow;
    
    public void TestA()
    {
        // 明确调度UI操作到主线程
        _mainWindow.Dispatcher.Invoke(() =>
        {
            _mainWindow.GoToPage();
        });
    }
    
  2. 验证ThreadManager的实现,确保它正确使用WPF的Dispatcher调度到主线程:

    public static class ThreadManager
    {
        public static void ExecuteOnMainThread(Action action)
        {
            if (Application.Current == null)
            {
                action.Invoke();
                return;
            }
            // 使用Dispatcher确保在主线程执行
            Application.Current.Dispatcher.Invoke(action);
        }
    }
    
  3. 检查GoToPage方法,确保内部所有UI操作都没有跨线程调用,若有子控件操作,同样需要通过Dispatcher调度。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 09:59:58