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

WPF客户端与多线程服务器通信:TextBlock显示异常求助

问题修复方案

核心问题分析

你的代码存在三个关键问题:

  1. WPF绑定未生效:未设置窗口的DataContext,导致PropertyChanged事件始终为null,绑定无法触发UI更新
  2. 异步TCP操作时机错误:BeginRead是异步操作,调用后立即读取缓冲区会拿到初始空值;ConnectAsync未等待完成就获取流,可能导致连接未建立就操作
  3. 服务器异步读取逻辑错误:服务器在调用BeginRead后立刻读取缓冲区,此时异步读取尚未完成,无法获取客户端发送的数据

具体修复步骤

1. 修复客户端绑定问题

在MainWindow构造函数中添加DataContext = this;,让UI绑定到当前窗口实例:

public MainWindow()
{
    InitializeComponent();
    DataContext = this; // 新增这行
}

同时修正Message属性的OnPropertyChanged调用,利用CallerMemberName自动获取属性名:

public string Message
{
    get => _message;
    set
    {
        _message = value;
        OnPropertyChanged(); // 无需手动传递属性名
    }
}

2. 修复客户端异步读取逻辑

BeginRead是异步操作,必须在EndRead回调中处理服务器返回的数据,并且要切换回UI线程更新界面:

public void EndRead(IAsyncResult result)
{
    byte[] buffer = (byte[])result.AsyncState;
    var ns = _Client.GetStream();
    int bytesRead = ns.EndRead(result);
    
    if (bytesRead > 0)
    {
        string resultStr = Encoding.UTF8.GetString(buffer, 0, bytesRead);
        // 切换到UI线程更新绑定属性
        Application.Current.Dispatcher.Invoke(() =>
        {
            Message = resultStr;
        });
    }
}

同时修正Button_Click中的异步连接逻辑,等待连接完成后再操作流:

private async void Button_Click(object sender, RoutedEventArgs e)
{
    _Client = new TcpClient();

    try
    {
        int numVal;
        bool isParsed = int.TryParse(TextBox_1.Text, out numVal);

        if (!isParsed || (numVal != 1 && numVal != 2))
            return;

        await _Client.ConnectAsync(_IP, _PORT); // 等待连接完成
        var Stream = _Client.GetStream();

        if (Stream != null && !string.IsNullOrEmpty(TextBox_1.Text))
        {
            byte[] buffer = Encoding.UTF8.GetBytes(TextBox_1.Text);
            await Stream.WriteAsync(buffer, 0, buffer.Length);
            await Stream.FlushAsync();

            byte[] bufferResult = new byte[_BUFFER_SIZE];
            Stream.BeginRead(bufferResult, 0, bufferResult.Length, EndRead, bufferResult);
            // 不要在此处立即读取bufferResult,等待EndRead回调处理
        }
    }
    catch (SocketException ex)
    {
        Application.Current.Dispatcher.Invoke(() =>
        {
            TextBlock_1.Text = ex.Message;
        });
    }
}

3. 修复服务器异步读取逻辑

服务器的ClientObject应在EndRead回调中获取客户端发送的数据,再调用SendMessage:

public ClientObject(TcpClient client)
{
    _Client = client;
    _Stream = _Client.GetStream();
    byte[] buffer = new byte[_BUFFER_SIZE];
    // 启动异步读取,在回调中处理数据
    _Stream.BeginRead(buffer, 0, buffer.Length, EndRead, buffer);
}

public void EndRead(IAsyncResult result)
{
    byte[] buffer = (byte[])result.AsyncState;
    int bytesRead = _Stream.EndRead(result);
    
    if (bytesRead > 0)
    {
        string resultStr = Encoding.UTF8.GetString(buffer, 0, bytesRead);
        SendMessage(resultStr);
        // 继续监听下一次读取
        _Stream.BeginRead(buffer, 0, buffer.Length, EndRead, buffer);
    }
}

同时将SendMessage改为async Task避免异常丢失:

async Task SendMessage(string str)
{
    try
    {
        if (!int.TryParse(str, out int val) || (val != 1 && val != 2))
            return;

        val = val == 1 ? 3 : 4;
        string response = val.ToString();
        byte[] buffer = Encoding.UTF8.GetBytes(response);
        await _Stream.WriteAsync(buffer, 0, buffer.Length);
        await _Stream.FlushAsync();
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

4. 优化客户端提示逻辑

新增Tip属性绑定到TextBlock,避免直接赋值覆盖绑定值:

private string _tip;
public string Tip
{
    get => _tip;
    set
    {
        _tip = value;
        OnPropertyChanged();
    }
}

private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
    try
    {
        TextBox textBox = (TextBox)sender;
        textBox.MaxLength = 1;
        int numVal;
        bool isParsed = int.TryParse(textBox.Text, out numVal);

        Tip = isParsed 
            ? (numVal is 1 or 2 ? string.Empty : "The value must be 1 or 2!") 
            : "The value must be 1 or 2!";
    }
    catch (Exception ex)
    {
        Tip = ex.Message;
    }
}

修改后的完整代码

服务器代码

using System.Text;
using System.Net.Sockets;
using System.Net;

namespace Server
{
    internal class ServerHandler
    {
        private const int _PORT = 8888;
        private const int _BUFFER_SIZE = 1;
        protected internal TcpListener _Listener { get; set; }

        protected internal async Task ListenAsync()
        {
            try
            {
                _Listener = new TcpListener(IPAddress.Any, _PORT);
                _Listener.Start();
                Console.WriteLine("Server started, listening on port 8888...");

                while (true)
                {
                    TcpClient tcpClient = await _Listener.AcceptTcpClientAsync();
                    Console.WriteLine("New client connected");
                    ClientObject clientObject = new ClientObject(tcpClient);
                }
            }
            catch (SocketException ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        ~ServerHandler()
        {
            _Listener?.Stop();
        }

        static void Main(string[] args)
        {
            try
            {
                ServerHandler handler = new();
                _ = handler.ListenAsync();
                Console.ReadKey();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        internal class ClientObject
        {
            protected internal TcpClient _Client { get; set; }
            protected internal NetworkStream _Stream { get; set; }

            public ClientObject(TcpClient client)
            {
                _Client = client;
                _Stream = _Client.GetStream();
                byte[] buffer = new byte[_BUFFER_SIZE];
                _Stream.BeginRead(buffer, 0, buffer.Length, EndRead, buffer);
            }

            public void EndRead(IAsyncResult result)
            {
                byte[] buffer = (byte[])result.AsyncState;
                try
                {
                    int bytesRead = _Stream.EndRead(result);
                    if (bytesRead > 0)
                    {
                        string resultStr = Encoding.UTF8.GetString(buffer, 0, bytesRead);
                        Console.WriteLine($"Received: {resultStr}");
                        _ = SendMessage(resultStr);
                        _Stream.BeginRead(buffer, 0, buffer.Length, EndRead, buffer);
                    }
                    else
                    {
                        Console.WriteLine("Client disconnected");
                        _Client.Close();
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    _Client.Close();
                }
            }

            async Task SendMessage(string str)
            {
                try
                {
                    if (!int.TryParse(str, out int val) || (val != 1 && val != 2))
                        return;

                    val = val == 1 ? 3 : 4;
                    string response = val.ToString();
                    byte[] buffer = Encoding.UTF8.GetBytes(response);
                    await _Stream.WriteAsync(buffer, 0, buffer.Length);
                    await _Stream.FlushAsync();
                    Console.WriteLine($"Sent response: {response}");
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
        }
    }
}

客户端代码(Client.xaml.cs)

using System;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Net.Sockets;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace Client
{
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private const string _IP = "127.0.0.1";
        private const int _PORT = 8888;
        private const int _BUFFER_SIZE = 1;
        private TcpClient _Client;
        private string _message;
        private string _tip;

        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;
        }

        public string Message
        {
            get => _message;
            set
            {
                _message = value;
                OnPropertyChanged();
            }
        }

        public string Tip
        {
            get => _tip;
            set
            {
                _tip = value;
                OnPropertyChanged();
            }
        }

        private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            try
            {
                TextBox textBox = (TextBox)sender;
                textBox.MaxLength = 1;
                int numVal;
                bool isParsed = int.TryParse(textBox.Text, out numVal);

                Tip = isParsed 
                    ? (numVal is 1 or 2 ? string.Empty : "The value must be 1 or 2!") 
                    : "The value must be 1 or 2!";
            }
            catch (Exception ex)
            {
                Tip = ex.Message;
            }
        }

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            _Client?.Close();
            _Client = new TcpClient();

            try
            {
                if (!int.TryParse(TextBox_1.Text, out int numVal) || numVal is not 1 and not 2)
                    return;

                await _Client.ConnectAsync(_IP, _PORT);
                var stream = _Client.GetStream();

                byte[] sendBuffer = Encoding.UTF8.GetBytes(TextBox_1.Text);
                await stream.WriteAsync(sendBuffer, 0, sendBuffer.Length);
                await stream.FlushAsync();

                byte[] receiveBuffer = new byte[_BUFFER_SIZE];
                stream.BeginRead(receiveBuffer, 0, receiveBuffer.Length, EndRead, receiveBuffer);
            }
            catch (SocketException ex)
            {
                Tip = ex.Message;
            }
        }

        public void EndRead(IAsyncResult result)
        {
            byte[] buffer = (byte[])result.AsyncState;
            try
            {
                int bytesRead = _Client.GetStream().EndRead(result);
                if (bytesRead > 0)
                {
                    string response = Encoding.UTF8.GetString(buffer, 0, bytesRead);
                    Application.Current.Dispatcher.Invoke(() =>
                    {
                        Message = response;
                    });
                }
            }
            catch (Exception ex)
            {
                Application.Current.Dispatcher.Invoke(() =>
                {
                    Tip = ex.Message;
                });
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

客户端XAML(Client.xaml)

<Window x:Class="Client.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="250" Width="300">
    <Grid>
        <Button x:Name="Button_1" Content="Send" FontSize="15" Width="100" Height="30" Margin="180, 10, 0, 160"
            Click="Button_Click" ClickMode="Press"/>

        <TextBox x:Name="TextBox_1" MaxLength="1" FontSize="15" Margin="10, 20, 120, 170" TextChanged="TextBox_TextChanged"/>

        <TextBlock Margin="10, 90, 10, 130" TextWrapping="Wrap" Text="{Binding Tip}" Foreground="Red"/>
        <TextBlock Margin="10, 120, 10, 100" TextWrapping="Wrap" Text="{Binding Message}" FontSize="20" FontWeight="Bold"/>
    </Grid>
</Window>

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

相关产品推荐
方舟 Agent Plan

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

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