WPF客户端与多线程服务器通信:TextBlock显示异常求助
问题修复方案
核心问题分析
你的代码存在三个关键问题:
- WPF绑定未生效:未设置窗口的
DataContext,导致PropertyChanged事件始终为null,绑定无法触发UI更新 - 异步TCP操作时机错误:
BeginRead是异步操作,调用后立即读取缓冲区会拿到初始空值;ConnectAsync未等待完成就获取流,可能导致连接未建立就操作 - 服务器异步读取逻辑错误:服务器在调用
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
相关产品推荐
相关产品推荐

