首页 > 其他分享 >异步数据加载与绑定设计: AsyncLoadHelper

异步数据加载与绑定设计: AsyncLoadHelper

时间:2024-05-28 14:34:20浏览次数:20  
标签:异步 绑定 private IsLoading AsyncLoadHelper public 加载

在现代应用程序开发中,异步数据加载和管理是一个普遍的需求。本文介绍了一个自定义的异步数据加载工具——AsyncLoadHelper。通过详细的设计和实现介绍,以及结合实际应用示例,展示了AsyncLoadHelper在简化异步操作、提高代码可读性和维护性方面的优势。

在开发过程中,异步数据加载常常涉及复杂的状态管理和异常处理。为了解决这些问题,提高开发效率,我开发了AsyncLoadHelper类。这个工具旨在提供一种通用且易于使用的异步数据加载和状态管理解决方案。

设计理念

AsyncLoadHelper的设计目标是实现以下功能:

  • 泛型支持:适用于多种类型的数据加载需求。
  • 状态管理:自动管理加载状态和异常信息。
  • 线程安全:使用异步锁确保同一时间只有一个加载任务在进行。
  • 取消支持:支持取消操作,防止资源浪费。

类定义和实现

下面是AsyncLoadHelper类的实现,包括支持带参数的异步数据加载版本。

AsyncLoadHelper<TData>

这个类适用于不需要额外参数的数据加载场景。

using Prism.Commands;
using Prism.Mvvm;
using System;
using System.Collections.ObjectModel;
using System.Threading;
using System.Threading.Tasks;

namespace AutoMold.Shared.Base
{
    public class AsyncLoadHelper<TData> : BindableBase, IDisposable
  {
      private TData _data;
      private bool _isLoading;
      private Exception _loadingException;
      private readonly Lazy<DelegateCommand> _loadCommand;
      private readonly Func<CancellationToken, Task<TData>> _dataLoadMethod;
      private CancellationTokenSource _cts;
      private readonly SemaphoreSlim _asyncLock = new SemaphoreSlim(1, 1);

      public TData Data
      {
          get => _data;
          set => SetProperty(ref _data, value);
      }

      public bool IsLoading
      {
          get => _isLoading;
          set => SetProperty(ref _isLoading, value);
      }

      public Exception LoadingException
      {
          get => _loadingException;
          set => SetProperty(ref _loadingException, value);
      }

      public DelegateCommand LoadCommand => _loadCommand.Value;

      public AsyncLoadHelper(Func<CancellationToken, Task<TData>> dataLoadMethod)
      {
          _dataLoadMethod = dataLoadMethod;
          _loadCommand = new Lazy<DelegateCommand>(() =>
              new DelegateCommand(async () => await ExecuteLoadDataAsync(), () => !IsLoading).ObservesProperty(() => IsLoading));
      }

      public virtual async Task ExecuteLoadDataAsync()
      {
          // 如果当前已经在加载数据,则直接返回,防止重复加载
          if (IsLoading) return;
          // 等待异步锁,确保同时只有一个加载任务在进行
          await _asyncLock.WaitAsync();

          // 取消当前的加载任务(如果有),并创建一个新的CancellationTokenSource
          _cts?.Cancel();
          _cts = new CancellationTokenSource();

          // 将IsLoading属性设置为true,表示当前正在加载数据
          IsLoading = true;
          // 重置LoadingException属性为null,清除之前的异常信息
          LoadingException = null;

          try
          {
              // 异步调用数据加载方法,并将结果赋值给Data属性
              Data = await _dataLoadMethod(_cts.Token);
          }
          catch (OperationCanceledException)
          {
              // 捕获操作取消异常,可以根据需要处理
              // 这里没有特殊处理,因为取消操作是预期的行为
              // Handle if needed
          }
          catch (Exception e)
          {
              // 捕获所有其他异常,并将异常赋值给LoadingException属性,以便在UI中显示或记录
              LoadingException = e;
          }
          finally
          {
              // 无论是否发生异常,都将IsLoading属性设置为false,表示加载任务已完成
              IsLoading = false;
              // 释放异步锁,允许其他加载任务执行
              _asyncLock.Release();
          }
      }

      public void Dispose()
      {
          _cts?.Cancel();
          _cts?.Dispose();
          _asyncLock.Dispose();
      }
  }
}

AsyncLoadHelper<TData, TParameter>

这个类适用于需要额外参数的数据加载场景。

using Prism.Commands;
using Prism.Mvvm;
using System;
using System.Collections.ObjectModel;
using System.Threading;
using System.Threading.Tasks;

namespace AutoMold.Shared.Base
{
    public class AsyncLoadHelper<TData, TParameter> : BindableBase, IDisposable
    {
        private TData _data;
        private bool _isLoading;
        private Exception _loadingException;
        private readonly Lazy<DelegateCommand<TParameter>> _loadCommand;
        private readonly Func<TParameter, CancellationToken, Task<TData>> _dataLoadMethod;
        private CancellationTokenSource _cts;
        private readonly SemaphoreSlim _asyncLock = new SemaphoreSlim(1, 1);

        public TData Data
        {
            get => _data;
            set => SetProperty(ref _data, value);
        }

        public bool IsLoading
        {
            get => _isLoading;
            set => SetProperty(ref _isLoading, value);
        }

        public Exception LoadingException
        {
            get => _loadingException;
            set => SetProperty(ref _loadingException, value);
        }

        public DelegateCommand<TParameter> LoadCommand => _loadCommand.Value;

        public AsyncLoadHelper(Func<TParameter, CancellationToken, Task<TData>> dataLoadMethod)
        {
            _dataLoadMethod = dataLoadMethod;
            _loadCommand = new Lazy<DelegateCommand<TParameter>>(() =>
                new DelegateCommand<TParameter>(async param => await ExecuteLoadDataAsync(param), _ => !IsLoading).ObservesProperty(() => IsLoading));
        }

        public virtual async Task ExecuteLoadDataAsync(TParameter parameter)
        {
            if (IsLoading) return;
            await _asyncLock.WaitAsync();

            _cts?.Cancel();
            _cts = new CancellationTokenSource();
            IsLoading = true;
            LoadingException = null;

            try
            {
                Data = await _dataLoadMethod(parameter, _cts.Token);
            }
            catch (OperationCanceledException)
            {
                // Handle if needed
            }
            catch (Exception e)
            {
                HandleException(e);
            }
            finally
            {
                IsLoading = false;
                _asyncLock.Release();
            }
        }

        protected virtual void HandleException(Exception exception)
        {
            // Handle the exception here, e.g., log it or show to user
            LoadingException = exception;
        }

        public void Dispose()
        {
            _cts?.Cancel();
            _cts?.Dispose();
            _asyncLock.Dispose();
        }
    }
}

应用实例

以下代码展示了如何在实际项目中使用AsyncLoadHelper类进行异步数据加载:

ViewModel部分

/// <summary>
/// 速度参数
/// </summary>
public AsyncLoadHelper<ObservableCollection<DIntVal>> EditableValsLoader { get; }
/// <summary>
/// 实时信息
/// </summary>
public AsyncLoadHelper<ObservableCollection<DIntVal>> ViewableValsLoader { get; }

public HomePageViewModel(ILogger<HomePageViewModel> logger, IDIntValGroupRepository dintValGroupRepo, 
    IPLCService plc)
{
    _logger = logger;
    _dintValGroupRepo = dintValGroupRepo;
    _plc = plc;

    _timer = new Timer();
    _timer.Interval = 150;
    _timer.Elapsed += SyncViewableFromPLC;

    _timerLazy = new Timer();
    _timerLazy.Interval = 1000;
    _timerLazy.Elapsed += SyncEditableToPLC;

    EditableValsLoader = new AsyncLoadHelper<ObservableCollection<DIntVal>>(async cts =>
    {
        var list = await _dintValGroupRepo.GetEditablesAsync();
        return new ObservableCollection<DIntVal>(list);
    });
    EditableValsLoader.LoadCommand.Execute();
    
    ViewableValsLoader = new AsyncLoadHelper<ObservableCollection<DIntVal>>(async cts =>
    {
        var list = await _dintValGroupRepo.GetViewablesAsync();
        return new ObservableCollection<DIntVal>(list);
    });
    ViewableValsLoader.LoadCommand.Execute();
}

XAML部分

<Grid Grid.Column="0">
    <GroupBox
        BorderBrush="{DynamicResource BorderColorDefault}"
        Header="实时信息"
        Style="{x:Null}">
        <ItemsControl
            Margin="5"
            d:ItemsSource="{x:Static vm:HomePageViewModel.DesignViewableVals}"
            FontSize="20"
            FontWeight="Bold"
            ItemsSource="{Binding ViewableValsLoader.Data}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <UniformGrid Columns="1" />
                </ItemsControl.ItemsPanel>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="120" />
                            <ColumnDefinition Width="120" />
                        </Grid.ColumnDefinitions>
                        <TextBlock
                            Grid.Column="0"
                            Margin="5"
                            HorizontalAlignment="Right"
                            VerticalAlignment="Center"
                            Text="{Binding DisplayName}" />
                        <TextBlock
                            Grid.Column="1"
                            HorizontalAlignment="Right"
                            VerticalAlignment="Center"
                            Style="{x:Null}"
                            Text="{Binding Val, StringFormat={}{0:F1}, Converter={StaticResource IntDivide10ToFloatConverter}}" />
                    </Grid>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </GroupBox>
    <hc:LoadingCircle
        Margin="6"
        HorizontalAlignment="Center"
        VerticalAlignment="Center"
        Foreground="{DynamicResource ForegroundDefault}"
        Visibility="{Binding ViewableValsLoader.IsLoading, Converter={StaticResource Boolean2VisibilityConverter}}" />
</Grid>

AsyncLoadHelper类通过提供一种通用、易于使用的异步数据加载和状态管理方案,极大地简化了异步编程中的复杂性。无论是无需额外参数的简单数据加载,还是需要参数的复杂场景,AsyncLoadHelper都能轻松应对。希望本文能帮助开发者更好地理解和应用AsyncLoadHelper,从而提高开发效率和代码质量。

标签:异步,绑定,private,IsLoading,AsyncLoadHelper,public,加载
From: https://www.cnblogs.com/linxmouse/p/18217960

相关文章

  • WPF 中具有静态属性的数据绑定
    我有一个简单的静态属性FontSizeTitle,它应该用于所有HandledWindow类型实例中的风格化标题,并在更改属性后同时从同一个静态属性更新而无需明确通知。通过设置面板或任何会更改属性的内容,以便直观地更改和更新所有窗口的所有标题的字体大小。这是我在XAML中风格化标题的代码,它......
  • WPF开发01-数据绑定的几种方式,静态,动态、向上查找、适应各种情况
    1.前后端简单绑定第一种比较常见,常见于mvvm框架前端<TextBlockText="{BindingPath=Name}"></TextBlock>1后端publicclassPersonViewModel:INotifyPropertyChanged{publicstringName{get{returnname;}set{if(name......
  • 第五章 并发基础中的Future异步回调模式
    案例:为了提升泡茶效率。下面分别是用阻塞模式和异步回调模式来实现其中的异步泡茶流程。为了异步执行整个泡茶流程,分别设计三条线程:主线程、清洗线程、烧水线程。(1)主线程(MainThread)的工作是:启动清洗线程、启动烧水线程,等清洗、烧水完成后,泡茶喝。(2)清洗线程(WashThread)的工作是:洗......
  • WPF在ListView中绑定Command命令的写法
    假定:ViewModel中有一个数据源叫Persons,有一个命令叫DoCommand,通过System.Windows.Interactivity触发器绑定鼠标MouseUp事件,当UI端绑定了DataContext数据上下文之后,Command="{BindingDoCommand}"是找不到这个命令的,必须使用Binging类的RelativeSource属性先找到当前UI,再找到DataC......
  • 【Python并发编程指南】多线程、多进程与异步编程比较与选择
    ......
  • C# 混淆加密大师1.1.0更新, 新增资源文件加密, 防虚拟机, 异步混淆等新功能
    C#混淆加密大师是一款强大的工具,专为保护C#开发的dll和exe文件而设计,适用于各种应用程序,包括Winform、WPF、Unity游戏以及控制台程序。它支持从.NetFramework2.0到.NetFramework4.x,以及.NETCore2.0直至最新的.NET8版本。C#混淆加密大师不仅提供代码加密混淆功能,还能对EXE文......
  • 同步通信与异步通信
    异步通信(AsynchronousCommunication)和同步通信(SynchronousCommunication)是计算机网络和计算机系统中两种不同的数据传输方式。它们的主要区别在于数据传输的控制方式和时间关系。同步通信(SynchronousCommunication)同步通信是指数据传输过程中,发送方和接收方的时钟是同步的,即......
  • 一对一视频源码,异步中出现了异常该如何处理?
    一对一视频源码,异步中出现了异常该如何处理?js本质上是同步的,是一种单线程语言。诸如浏览器引擎之类的宿主环境使用许多WebAPI,增强了js以与外部系统进行交互并处理与I/O绑定的操作。浏览器中异步操作有:定时器相关的函数、事件、Ajax请求和Promise等。一、定时器的错误处理......
  • Python异步编程之基础概念
    Python异步编程之基础概念在现代编程中,异步编程是一种重要的技术,尤其是在处理I/O密集型任务时,异步编程可以大大提高程序的性能和响应速度。本文将介绍Python异步编程的基础概念,帮助你理解其原理和应用。什么是异步编程?异步编程是一种并发编程模型,它允许程序在等待某些任......
  • 深入解析Python并发的多线程和异步编程
    在Python编程中,多线程是一种常用的并发编程方式,它可以有效地提高程序的执行效率,特别是在处理I/O密集型任务时。Python提供了threading模块,使得多线程编程变得相对简单。本文将深入探讨threading模块的基础知识,并通过实例演示多线程的应用。1.多线程基础概念在开始之前,让我们......