桌面图片应用加载大量图片时UI卡顿:如何实现异步加载?
问题场景
我正在开发一款简易桌面图片应用,目前整体进展顺利。该应用从文件夹中获取图片,通过ItemsControl按(MM)YYYY格式的Key分组展示,数据基于StorageItem类。少量图片时应用运行正常,但图片数量较多时页面会卡顿,原因是Icon属性(BitmapImage类型)在MediaPage的GridView展示前就已全部加载,阻塞了UI线程。
相关XAML代码:
<ItemsControl x:Name="test_control"> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Margin="10"> <!-- Display the Key (Year and Month) --> <TextBlock Text="{Binding Key}" FontSize="20" FontWeight="Bold" Margin="0,0,0,10" /> <GridView ItemsSource="{Binding Items}" SelectionMode="None" IsItemClickEnabled="True" RightTapped="GridView_RightTapped" DoubleTapped="GridView_DoubleTapped"> <GridView.ItemsPanel> <ItemsPanelTemplate> <ItemsWrapGrid MaximumRowsOrColumns="20" Orientation="Horizontal" /> </ItemsPanelTemplate> </GridView.ItemsPanel> <GridView.ItemTemplate> <DataTemplate> <Grid Width="100" Height="100" ToolTipService.ToolTip="{Binding FullPath}"> <StackPanel Orientation="Vertical"> <Image Height="90" Width="90" Source="{Binding Icon}" Stretch="UniformToFill"/> </StackPanel> <ProgressRing VerticalAlignment="Bottom" HorizontalAlignment="Right" Visibility="{Binding ProgressBarVisiable}" Value="{Binding Value}" IsIndeterminate="False" Height="25" Width="25"/> <FontIcon HorizontalAlignment="Left" VerticalAlignment="Bottom" Glyph="{Binding StatusFont}"/> </Grid> </DataTemplate> </GridView.ItemTemplate> </GridView> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl>
核心后台代码片段:
- MediaPage.cs
test.ItemsSource = App.AllImages;
- MainPage.cs
App.AllImages = new ObservableCollection<MediaDynamicView>(); PublicMethods.GetAllImages(AllImages);
- ImageItem.cs
// more properties... public virtual BitmapImage Icon { get { return _icon; } set { _icon = value; OnPropertyChanged(); } }
异步加载Icon的解决方案
1. 修改ImageItem实现延迟异步加载
将Icon的加载逻辑改为异步,仅当Item被UI渲染时才触发加载,同时维护加载状态控制ProgressRing显示:
private BitmapImage _icon; private bool _isLoading; // 新增加载状态属性,绑定到ProgressRing的可见性 public bool IsLoading { get => _isLoading; set { _isLoading = value; OnPropertyChanged(); } } public virtual BitmapImage Icon { get { // 仅当未加载且未在加载中时触发异步加载 if (_icon == null && !IsLoading) { _ = LoadIconAsync(); } return _icon; } set { _icon = value; OnPropertyChanged(); } } private async Task LoadIconAsync() { IsLoading = true; try { var bitmap = new BitmapImage(); // 设置解码尺寸,匹配UI上的图片大小,减少内存占用 bitmap.DecodePixelWidth = 90; // UWP环境下的加载方式 using (var stream = await StorageFile.GetFileFromPathAsync(FullPath).AsTask()) using (var randomAccessStream = await stream.OpenAsync(FileAccessMode.Read)) { await bitmap.SetSourceAsync(randomAccessStream); } // 如果是WPF环境,替换为以下代码: // bitmap.BeginInit(); // bitmap.CacheOption = BitmapCacheOption.OnLoad; // bitmap.UriSource = new Uri(FullPath); // bitmap.DecodePixelWidth = 90; // bitmap.EndInit(); Icon = bitmap; } catch (Exception) { // 加载失败时设置默认图标 Icon = new BitmapImage(new Uri("ms-appx:///Assets/DefaultThumbnail.png")); } finally { IsLoading = false; } }
2. 更新XAML绑定
修改ProgressRing的绑定,关联新增的IsLoading属性,同时使用布尔值转可见性的转换器:
首先在页面资源中定义转换器(若项目中没有):
<Page.Resources> <local:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" /> </Page.Resources>
转换器代码:
public class BooleanToVisibilityConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, string language) { return (value is bool loading && loading) ? Visibility.Visible : Visibility.Collapsed; } public object ConvertBack(object value, Type targetType, object parameter, string language) { return (value is Visibility visibility && visibility == Visibility.Visible); } }
然后更新ProgressRing的绑定:
<ProgressRing VerticalAlignment="Bottom" HorizontalAlignment="Right" Visibility="{Binding IsLoading, Converter={StaticResource BooleanToVisibilityConverter}}" IsIndeterminate="True" Height="25" Width="25"/>
3. 优化UI虚拟化
确保ItemsControl和GridView的虚拟化正常工作,减少不必要的Item渲染:
- 给外层ItemsControl添加虚拟化面板:
<ItemsControl x:Name="test_control"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <VirtualizingStackPanel /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <!-- 原有ItemTemplate不变 --> </ItemsControl>
- GridView的ItemsWrapGrid默认支持虚拟化,无需额外修改,但要避免给ItemContainer添加复杂布局或样式,影响虚拟化效率。
4. 批量加载优化(可选)
如果希望控制并发加载数量,避免同时加载过多图片导致资源占用过高,可以在ImageItem中加入静态的信号量控制:
private static readonly SemaphoreSlim _loadSemaphore = new SemaphoreSlim(5); // 最多同时加载5张 private async Task LoadIconAsync() { IsLoading = true; await _loadSemaphore.WaitAsync(); try { // 原有加载逻辑 } finally { IsLoading = false; _loadSemaphore.Release(); } }
内容的提问来源于stack exchange,提问作者Ariel P
相关产品推荐
相关产品推荐

