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

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();
    }
}   

优化后的简洁实现方案

核心优化点

  1. 移除自定义ProgressChanged事件,直接让SyncService接收IProgress<ProgressInfo>参数,减少冗余的事件订阅逻辑
  2. 用RelayCommand驱动同步操作,严格遵循MVVM命令模式
  3. 替换Thread.Sleep为Task.Delay,符合异步编程规范,避免阻塞线程
  4. 简化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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 06:07:05