.NET MAUI中TCP监听器正常但CollectionView UI不更新求助
MAUI中TCP监听实时更新CollectionView界面冻结问题
我是MAUI新手,遇到TCP监听器相关问题:监听器本身工作正常,但UI中的CollectionView无法实时显示信号,界面处于冻结状态。当点击CollectionView时会显示缓存的信号,之后又停止更新,直到选中行发生变化。我尝试过使用async Task和Thread,但都未解决问题,请问我哪里出错了?
原C#代码
using System.Net; using System.Net.Sockets; using System.Text; using System.Xml; using Microsoft.Maui.Controls; using Npgsql; namespace Black_Program; public partial class SignalsPage : ContentPage { public static bool searchRedy = true; public static int daysNotPayedAlarm = 60; System.Threading.Thread ReadTerminalSignals; public SignalsPage() { InitializeComponent(); Task.Run(async () => await ReadFromIp()); } async Task ReadFromIp() { var terminalSignalsList = new List<string>(); int terminalSignalsCount = 0; TcpClient tcpclnt = new TcpClient(); try { while (true) { try { tcpclnt.Connect(MainPage.sqlServerIP, MainPage.sqlServerTerminalPort); } catch (Exception) { } while (tcpclnt.Connected) { Thread.Sleep(10); string messege = string.Empty; Stream stream = tcpclnt.GetStream(); byte[] bytes = new byte[1024]; int length = stream.Read(bytes, 0, 1024); /* Convert from ascii to stream */ for (int i = 0; i < length; i++) messege += (Convert.ToChar(bytes[i])); //byte[] utfBytes = Encoding.Default.GetBytes(messege); messege = Encoding.UTF8.GetString(bytes); try { ////Update new signal if object selected string[] newMessages = messege .Split(new char[] { '-' } , StringSplitOptions.RemoveEmptyEntries) .ToArray(); if (signals_number_search.Text == newMessages[0].TrimEnd()) { string numberData = signals_number_search.Text.Replace(" ", string.Empty); ;// Read the input from the user. DateTime time = DateTime.Now; string dateToday = time.AddDays(1).ToString("yyyy'-'MM'-'dd"); // 2 days log string dateBefore = time.AddDays(-5).ToString("yyyy'-'MM'-'dd"); ShowSignalsFromLogDB(numberData, dateToday, dateBefore); } } catch (Exception) { } //Application.Current.MainPage.Dispatcher.Dispatch(() => terminalSignalsList.Add(messege)); terminalSignalsList.Add(messege); signals_terminal.ItemsSource = terminalSignalsList; terminalSignalsCount++; Application.Current.MainPage.Dispatcher.Dispatch(() => signals_terminal.ScrollTo(terminalSignalsCount)); } } } catch (Exception) { } }
原UI代码
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Black_Program.SignalsPage" Title="Сигнали"> <ScrollView BackgroundColor="Black"> <!-- Navigation Menu--> <!-- Test design --> <Grid> <Grid.RowDefinitions> <RowDefinition Height="AUTO"/><!--0--> <RowDefinition Height="AUTO"/><!--1--> <RowDefinition Height="AUTO"/><!--2--> <RowDefinition Height="AUTO"/><!--3--> <RowDefinition Height="AUTO"/><!--4--> <RowDefinition Height="AUTO"/><!--5--> <RowDefinition Height="AUTO"/><!--6--> <RowDefinition Height="AUTO"/><!--7--> </Grid.RowDefinitions> <!-- Cool Adres and Users --> <HorizontalStackLayout HeightRequest="{OnPlatform Default='40',iOS='40',Android='35'}" Margin="{OnPlatform Default='5,0,10,0',iOS='5,0,5,0',Android='5,0,5,0'}" Grid.Row="1" HorizontalOptions="CenterAndExpand"> <ScrollView> <CollectionView MaximumHeightRequest="300" x:Name="signals_terminal" SelectionMode="Single"> <CollectionView.ItemsSource> <x:Array Type="{x:Type x:String}"> <x:String>Няма данни</x:String> </x:Array> </CollectionView.ItemsSource> </CollectionView> </ScrollView> </Frame> </Grid> </ScrollView> </ContentPage>
问题根源分析
- UI线程操作违规:直接在后台线程修改数据源并设置
ItemsSource,MAUI要求所有UI操作必须在主线程执行,否则会导致界面更新滞后或冻结。 - 集合无变更通知:使用
List<string>作为数据源,它不实现INotifyCollectionChanged接口,CollectionView无法自动感知集合变化,只有手动重置ItemsSource时才会更新,这就是点击后才显示缓存数据的原因。 - 阻塞式IO操作:
Thread.Sleep(10)和同步的stream.Read会阻塞后台线程,降低数据处理效率,拖慢UI更新节奏。 - 编码转换错误:先通过ASCII拼接字符再用UTF8转换整个字节数组,会导致乱码或数据截断,且未使用读取到的
length参数,会包含字节数组中的空字符。
修正方案
1. 使用可观察集合替换普通List
将List<string>改为ObservableCollection<string>,它会自动通知CollectionView集合变化,无需反复设置ItemsSource。
2. 所有UI操作切换到主线程
用Dispatcher.Dispatch包裹所有涉及UI元素的代码,包括集合添加、控件属性访问等。
3. 替换阻塞式操作为异步IO
使用await stream.ReadAsync替代同步stream.Read,去掉Thread.Sleep,避免阻塞线程。
4. 修复编码转换逻辑
仅转换实际读取到的字节数据,避免乱码。
修正后的代码
C#代码
using System.Net; using System.Net.Sockets; using System.Text; using System.Collections.ObjectModel; using Microsoft.Maui.Controls; namespace Black_Program; public partial class SignalsPage : ContentPage { public static bool searchRedy = true; public static int daysNotPayedAlarm = 60; private readonly ObservableCollection<string> _terminalSignalsList = new(); public SignalsPage() { InitializeComponent(); signals_terminal.ItemsSource = _terminalSignalsList; _ = ReadFromIp(); } async Task ReadFromIp() { while (true) { using var tcpclnt = new TcpClient(); try { await tcpclnt.ConnectAsync(MainPage.sqlServerIP, MainPage.sqlServerTerminalPort); using var stream = tcpclnt.GetStream(); var buffer = new byte[1024]; while (tcpclnt.Connected) { int length = await stream.ReadAsync(buffer, 0, buffer.Length); if (length <= 0) break; string message = Encoding.UTF8.GetString(buffer, 0, length); Dispatcher.Dispatch(() => { _terminalSignalsList.Add(message); signals_terminal.ScrollTo(_terminalSignalsList.Count - 1, position: ScrollToPosition.End); }); await Dispatcher.DispatchAsync(async () => { try { string searchText = signals_number_search.Text?.Trim() ?? string.Empty; if (string.IsNullOrEmpty(searchText)) return; string[] newMessages = message.Split('-', StringSplitOptions.RemoveEmptyEntries); if (newMessages.Length > 0 && newMessages[0].TrimEnd() == searchText) { string numberData = searchText.Replace(" ", string.Empty); DateTime now = DateTime.Now; string dateToday = now.AddDays(1).ToString("yyyy-MM-dd"); string dateBefore = now.AddDays(-5).ToString("yyyy-MM-dd"); await ShowSignalsFromLogDB(numberData, dateToday, dateBefore); } } catch { } }); } } catch { await Task.Delay(1000); } } } private Task ShowSignalsFromLogDB(string numberData, string dateToday, string dateBefore) { // 实现数据库查询逻辑 return Task.CompletedTask; } }
UI代码(修正闭合标签并移除硬编码数据源)
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Black_Program.SignalsPage" Title="Сигнали"> <ScrollView BackgroundColor="Black"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="AUTO"/><!--0--> <RowDefinition Height="AUTO"/><!--1--> <RowDefinition Height="AUTO"/><!--2--> <RowDefinition Height="AUTO"/><!--3--> <RowDefinition Height="AUTO"/><!--4--> <RowDefinition Height="AUTO"/><!--5--> <RowDefinition Height="AUTO"/><!--6--> <RowDefinition Height="AUTO"/><!--7--> </Grid.RowDefinitions> <HorizontalStackLayout HeightRequest="{OnPlatform Default='40',iOS='40',Android='35'}" Margin="{OnPlatform Default='5,0,10,0',iOS='5,0,5,0',Android='5,0,5,0'}" Grid.Row="1" HorizontalOptions="CenterAndExpand"> <ScrollView> <CollectionView MaximumHeightRequest="300" x:Name="signals_terminal" SelectionMode="Single"> </CollectionView> </ScrollView> </HorizontalStackLayout> </Grid> </ScrollView> </ContentPage>
额外注意事项
- 所有UI元素的访问和修改必须通过
Dispatcher切换到主线程。 - 使用
using语句管理TcpClient和Stream的生命周期,避免资源泄漏。 - 连接失败后添加延迟重试,防止频繁重试占用过高CPU。
- 数据库操作需使用异步方法,避免阻塞线程。
内容的提问来源于stack exchange,提问作者Luki
相关产品推荐
相关产品推荐

