MAUI中实现长时任务并在UI展示进度的优化方案咨询
MAUI 长时间操作进度反馈的简洁实现方案
需求描述
我需要实现一种简单方式来执行长时间运行的操作(例如从中央服务器数据库同步到设备本地数据库),并在UI上提供进度的可视化反馈。操作通过模态内容页(Navigation.PushAsync)触发,用户在同步期间无法进行其他操作(本示例不包含取消操作),同时能实时了解进度。操作逻辑封装在独立类中,通过事件传递进度信息。作为MAUI新手,我折腾了数日才找到一个可行但略显繁琐的方案(曾遇到UI不更新问题),且尽量遵循MVVM模式,想了解更优、更简洁的实现方式。
现有实现方案(基于CommunityToolkit.MVVM)
1. 进度信息传递类
public class ProgressInfo { /// <summary> /// 进度百分比(0到1之间) /// </summary> public double ProgressPerc { get; set; } = 0; /// <summary> /// 操作进度相关的额外信息 /// </summary> public object ProgressData { get; set; } }
2. 长时间操作实现类(模拟示例)
public class SyncService { private ProgressInfo _progressInfo = new ProgressInfo() { ProgressPerc=0, ProgressData="同步进行中..." }; // 用于传递操作进度的事件 public event EventHandler<ProgressInfo> ProgressChanged; protected virtual void OnProgressChanged(ProgressInfo progress) { ProgressChanged?.Invoke(this, progress); } // 核心操作方法 public async Task<bool> ReceiveFromServerAsync(DateTime StartDate) { //------ 模拟长时间运行操作 --------------------------- for (int i = 0; i <= 10; i++) { _progressInfo.ProgressPerc = (double)i / 10; OnProgressChanged(_progressInfo); Thread.Sleep(500); } //--------------------------------------------------------------- return true; } }
3. 进度反馈内容页(XAML)
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:dx="http://schemas.devexpress.com/maui" xmlns:local="clr-namespace:SoEasyMobile" x:Class="SoEasyMobile.UI.Generic.DataSyncPage" x:DataType="local:UI.Generic.DataSyncVModel" Title="同步页面" Shell.PresentationMode="ModalAnimated"> <VerticalStackLayout> <Label Padding="40" Text="这是一个同步示例页面" VerticalOptions="Center" HorizontalOptions="Center" FontSize="Title"/> <VerticalStackLayout Padding="50,20" Spacing="20"> <Label x:Name="lblMessage" Text="{Binding ProgressMessage}" HorizontalOptions="Center" FontSize="Large" /> <dx:RadialProgressBar Value="{Binding ProgressPerc}" StartAngle="135" SweepAngle="270" WidthRequest="150" Padding="0,30"> </dx:RadialProgressBar> <Button Text="关闭" Clicked="btnClose_Clicked" IsEnabled="{Binding Finished}" /> </VerticalStackLayout> </VerticalStackLayout> </ContentPage>
4. 页面ViewModel
public partial class DataSyncVModel : BaseVModel { SyncService Sync; IProgress<ProgressInfo> Progress; public DataSyncVModel() { Sync = new SyncService(); Sync.ProgressChanged += Sync_ProgressChanged; } [ObservableProperty] double _progressPerc = 0; [ObservableProperty] string _progressMessage = string.Empty; [ObservableProperty] bool _finished = false; // 供视图调用,更新绑定的进度属性 public void ChangeProgressPerc(ProgressInfo progress) { ProgressPerc = progress.ProgressPerc; Finished = (progress.ProgressPerc >= 1); if (progress.ProgressData != null) { ProgressMessage = progress.ProgressData.ToString(); } } // 供视图代码后置直接调用的同步方法 public async void DoSyncWithProgress(IProgress<ProgressInfo> progress) { Progress = progress; bool ret = await Sync.ReceiveFromServerAsync(DateTime.Now); } private void Sync_ProgressChanged(object sender, ProgressInfo e) { // 注意:如果直接在这里更新ProgressPerc属性,UI不会更新 // 使用IProgress对象来报告进度 if (Progress != null) Progress.Report(e); } }
5. 页面代码后置
public partial class DataSyncPage : ContentPage { DataSyncVModel vm; public DataSyncPage() { InitializeComponent(); vm = new DataSyncVModel(); BindingContext = vm; } // 页面可见时触发 protected async override void OnNavigatedTo(NavigatedToEventArgs args) { base.OnNavigatedTo(args); // 注意:此处处于UI线程,因此lambda也会在UI线程执行 var progress = new System.Progress<ProgressInfo>(progressValue => { // 调用ViewModel方法更新绑定属性 vm.ChangeProgressPerc(progressValue); }); await Task.Run(() => vm.DoSyncWithProgress(progress)); } private async void btnClose_Clicked(object sender, EventArgs e) { await Navigation.PopModalAsync(); } }
优化后的简洁实现方案
核心优化点
- 移除自定义
ProgressChanged事件,直接让SyncService接收IProgress<ProgressInfo>参数,减少冗余的事件订阅逻辑 - 用
RelayCommand驱动同步操作,严格遵循MVVM命令模式 - 替换
Thread.Sleep为Task.Delay,符合异步编程规范,避免阻塞线程 - 简化UI线程调度逻辑,利用
Progress<T>自动在创建线程(UI线程)执行回调的特性,解决UI不更新问题
优化后的SyncService
public class SyncService { // 直接接收IProgress参数,无需自定义事件 public async Task<bool> ReceiveFromServerAsync(DateTime startDate, IProgress<ProgressInfo> progress) { var progressInfo = new ProgressInfo { ProgressData = "同步进行中..." }; //------ 模拟长时间运行操作 --------------------------- for (int i = 0; i <= 10; i++) { progressInfo.ProgressPerc = (double)i / 10; progress.Report(progressInfo); // 使用Task.Delay替代Thread.Sleep,避免阻塞异步线程 await Task.Delay(500); } //--------------------------------------------------------------- return true; } }
优化后的ViewModel
public partial class DataSyncVModel : BaseVModel { private readonly SyncService _syncService; public DataSyncVModel() { _syncService = new SyncService(); } [ObservableProperty] double _progressPerc = 0; [ObservableProperty] string _progressMessage = string.Empty; [ObservableProperty] bool _finished = false; // 用RelayCommand触发同步,符合MVVM规范 [RelayCommand] public async Task StartSyncAsync() { Finished = false; // 创建IProgress,自动在UI线程执行回调 var progress = new Progress<ProgressInfo>(info => { ProgressPerc = info.ProgressPerc; ProgressMessage = info.ProgressData.ToString(); Finished = info.ProgressPerc >= 1; }); await _syncService.ReceiveFromServerAsync(DateTime.Now, progress); } }
优化后的页面代码后置
public partial class DataSyncPage : ContentPage { private readonly DataSyncVModel _vm; public DataSyncPage() { InitializeComponent(); _vm = new DataSyncVModel(); BindingContext = _vm; } protected override async void OnNavigatedTo(NavigatedToEventArgs args) { base.OnNavigatedTo(args); // 直接调用ViewModel的命令启动同步 await _vm.StartSyncAsyncCommand.ExecuteAsync(null); } private async void btnClose_Clicked(object sender, EventArgs e) { await Navigation.PopModalAsync(); } }
优化说明
- 去掉了事件订阅与转发的冗余环节,代码结构更简洁
- 命令驱动的方式让ViewModel职责更清晰,减少视图与ViewModel的耦合
- 异步操作更规范,避免线程阻塞问题
- 利用
Progress<T>的内置线程调度,无需手动处理UI线程切换,彻底解决UI不更新问题
内容的提问来源于stack exchange,提问作者Ubaldo Zambelli
相关产品推荐
相关产品推荐

