WPF Prism.Wpf and Prism.DryIOC are both -v 8.1.97,implement mvvm,ioc,auto wire viewmodel ,MemoryCache,listbox loaded 10M pictures

Install-Package Prism.Wpf -v 8.1.97;
Install-Package Prism.DryIOC -v 8.1.97;
Install-Package System.Runtime.Caching;

 

//memory cache
CacheItemPolicy cachePolicy;
private readonly MemoryCache imageCache;
public MainWindowViewModel()
{
    // 1. 配置缓存参数(核心:限制内存占用)
var cacheConfig = new NameValueCollection
{
    // 缓存最大内存限制:512MB(根据你的服务器/客户端配置调整)
    { "cacheMemoryLimitMegabytes", "512" },
    // 物理内存占用百分比阈值:超过80%则自动清理缓存
    { "physicalMemoryLimitPercentage", "80" },
    // 缓存轮询清理间隔:2分钟检查一次过期项
    { "pollingInterval", "00:02:00" }
};

// 初始化自定义缓存
imageCache = new MemoryCache("ImageCache", cacheConfig);

//  配置缓存策略(CacheItemPolicy)
cachePolicy = new CacheItemPolicy
{
    // 滑动过期:30分钟未访问则移除
    SlidingExpiration = TimeSpan.FromMinutes(30),
    // 绝对过期(可选,与滑动过期二选一/同时用):最多缓存2小时
    // AbsoluteExpiration = DateTimeOffset.Now.AddHours(2),
    // 缓存优先级:低优先级会优先被清理
    Priority = CacheItemPriority.Default,
    // 缓存移除时的回调(可选,用于监控)
    RemovedCallback = CacheItemRemovedCallback
};
}


private ImageSource GetImgSource(string imgUrl)
{
    if(!File.Exists(imgUrl))
    {
        return null;
    }

    if(imageCache.Get(imgUrl) is ImageSource imgSource)
    {
        return imgSource;
    }

    BitmapImage bmi = new BitmapImage();
    bmi.BeginInit();
    bmi.UriSource = new Uri(imgUrl, UriKind.RelativeOrAbsolute);
    // 千万级列表必须用OnDemand,否则内存直接溢出
    bmi.CacheOption = BitmapCacheOption.OnDemand;
    // 关键优化:延迟创建图片帧(进一步降低内存)
    bmi.CreateOptions = BitmapCreateOptions.DelayCreation;
    // 禁用自动旋转提升性能
    bmi.Rotation = Rotation.Rotate0;
    bmi.EndInit();
    if(bmi.CanFreeze)
    {
        bmi.Freeze();
    }            

    // 将图片加入缓存
    imageCache.Add(imgUrl, bmi, cachePolicy);
    return bmi;
}

/// <summary>
/// 缓存项被移除时的回调(监控/日志用)
/// </summary>
private void CacheItemRemovedCallback(CacheEntryRemovedArguments args)
{
    System.Diagnostics.Debug.WriteLine($"缓存项被移除:{args.CacheItem.Key},原因:{args.RemovedReason}");
}

 

 

 

 

image

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

image

 

image

 

//App.xaml
<prism:PrismApplication x:Class="WpfApp47.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WpfApp47"
             xmlns:prism="http://prismlibrary.com/">
</prism:PrismApplication>

//App.xaml.cs
using System.Configuration;
using System.Data;
using System.Windows;
using Prism.DryIoc;
using Prism.Ioc;

namespace WpfApp47
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : PrismApplication
    {
        protected override Window CreateShell()
        {
            return Container.Resolve<MainWindow>();
        }

        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
            containerRegistry.Register<IIDService, IDService>();
            containerRegistry.Register<INameService, NameService>();
            containerRegistry.Register<IISBNService, ISBNService>();
            containerRegistry.Register<MainWindowViewModel>();
        }
    }

    public interface IIDService
    {
        int GetID();
    }
    public class IDService : IIDService
    {
        int id = 0;

        public int GetID()
        {
            return Interlocked.Increment(ref id);
        }
    }

    public interface INameService
    {
        string GetName();
    }

    public class NameService : INameService
    {
        int idx = 0;

        public string GetName()
        {
            return $"Name_{Interlocked.Increment(ref idx)}";
        }
    }

    public interface IISBNService
    {
        string GetISBN();
    }

    public class ISBNService : IISBNService
    {
        int idx = 0;

        public string GetISBN()
        {
            return $"ISBN_{Interlocked.Increment(ref idx)}_{Guid.NewGuid():N}";
        }
    }
}


//MainWindow.xaml
<Window x:Class="WpfApp47.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp47"
        xmlns:prism="http://prismlibrary.com/"
        prism:ViewModelLocator.AutoWireViewModel="True"
        WindowState="Maximized"
        mc:Ignorable="d"
        Title="{Binding MainTitle}" Height="450" Width="800">
    <Grid>
        <ListBox ItemsSource="{Binding BooksCollection,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                 VirtualizingPanel.IsVirtualizing="True"
                 VirtualizingPanel.VirtualizationMode="Recycling"
                 VirtualizingPanel.CacheLengthUnit="Item"
                 VirtualizingPanel.CacheLength="2,2"
                 ScrollViewer.CanContentScroll="True"
                 ScrollViewer.IsDeferredScrollingEnabled="True"
                 >
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Grid Width="{Binding DataContext.GridWidth,RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                          Height="{Binding DataContext.GridHeight,RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                          >
                        <Grid.Background>
                            <ImageBrush ImageSource="{Binding ImgSource}" Stretch="Uniform"/>
                        </Grid.Background>
                        <Grid.Resources>
                            <Style TargetType="TextBlock">
                                <Setter Property="FontSize" Value="50"/>
                                <Style.Triggers>
                                    <Trigger Property="IsMouseOver" Value="True">
                                        <Setter Property="FontSize" Value="100"/>
                                        <Setter Property="Foreground" Value="Red"/>
                                    </Trigger>
                                </Style.Triggers>
                            </Style>
                        </Grid.Resources>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition/>
                            <ColumnDefinition/>
                        </Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition/>
                            <RowDefinition/> 
                        </Grid.RowDefinitions>
                        <TextBlock Text="{Binding Id}" Grid.Row="0" Grid.Column="0"/>
                        <TextBlock Text="{Binding Name}" Grid.Row="0" Grid.Column="1"/>
                        <TextBlock Text="{Binding ISBN}" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"/>
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>


//MainWindow.xaml.cs include MainWindowViewModel class

using ImTools;
using Prism.Mvvm;
using System.Collections.Concurrent;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Runtime.Caching;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApp47
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.Loaded += async (s, e) =>
            {
                var vm = this.DataContext as MainWindowViewModel;
                if (vm != null)
                {
                    vm.GridWidth = this.ActualWidth;
                    vm.GridHeight = this.ActualHeight;
                    await vm.InitBooksCollectionAsync();
                }
            };
        }
    }

    public class MainWindowViewModel : BindableBase
    {
        IIDService idService;
        INameService nameService;
        IISBNService isbnService;
        //private ConcurrentDictionary<string, ImageSource> imgCache = new ConcurrentDictionary<string, ImageSource>();
        CacheItemPolicy cachePolicy;
        // 自定义MemoryCache实例(替代静态Default,便于控制生命周期)
        private readonly MemoryCache imageCache;
        public MainWindowViewModel(IIDService idServiceValue,INameService nameServiceValue,IISBNService isbnServiceValue)
        {
            idService=idServiceValue;
            nameService=nameServiceValue;
            isbnService=isbnServiceValue;

            // 1. 配置缓存参数(核心:限制内存占用)
            var cacheConfig = new NameValueCollection
            {
                // 缓存最大内存限制:512MB(根据你的服务器/客户端配置调整)
                { "cacheMemoryLimitMegabytes", "512" },
                // 物理内存占用百分比阈值:超过80%则自动清理缓存
                { "physicalMemoryLimitPercentage", "80" },
                // 缓存轮询清理间隔:2分钟检查一次过期项
                { "pollingInterval", "00:02:00" }
            };

            // 初始化自定义缓存
            imageCache = new MemoryCache("ImageCache", cacheConfig);

            //  配置缓存策略(CacheItemPolicy)
            cachePolicy = new CacheItemPolicy
            {
                // 滑动过期:30分钟未访问则移除
                SlidingExpiration = TimeSpan.FromMinutes(30),
                // 绝对过期(可选,与滑动过期二选一/同时用):最多缓存2小时
                // AbsoluteExpiration = DateTimeOffset.Now.AddHours(2),
                // 缓存优先级:低优先级会优先被清理
                Priority = CacheItemPriority.Default,
                // 缓存移除时的回调(可选,用于监控)
                RemovedCallback = CacheItemRemovedCallback
            };
        }

        public async Task InitBooksCollectionAsync()
        {
            string imgDir = @"../../../Images";
            if (!Directory.Exists(imgDir))
            {
                return;
            }
            var imgs = Directory.GetFiles(imgDir);
            int imgsCount = imgs.Count();

            BooksCollection = new ObservableCollection<Book>();
            List<Book> booksList = new List<Book>();
            for(int i=1;i<10000001;i++)
            {
                booksList.Add(new Book()
                {
                    Id=idService.GetID(),
                    Name=nameService.GetName(),
                    ISBN= isbnService.GetISBN(),
                    ImgSource = GetImgSource(imgs[i%imgsCount]) 
                });

                if(i%1000000==0)
                {
                    var tempList = booksList.ToList();
                    booksList.Clear();
                    await Application.Current.Dispatcher.InvokeAsync(() =>
                    {
                        foreach(var bk in tempList)
                        {
                            BooksCollection.Add(bk);
                        }
                        MainTitle = $"{DateTime.Now},loaded {BooksCollection.Count} items,{GetMemory()}";
                    }, System.Windows.Threading.DispatcherPriority.Background);
                }
            }
        }

        private string GetMemory()
        {
            var proc = Process.GetCurrentProcess().PrivateMemorySize64;
            return $"memory:{proc / 1024 / 1024:F2} M";
        }

        private ImageSource GetImgSource(string imgUrl)
        {
            if(!File.Exists(imgUrl))
            {
                return null;
            }

            if(imageCache.Get(imgUrl) is ImageSource imgSource)
            {
                return imgSource;
            }

            BitmapImage bmi = new BitmapImage();
            bmi.BeginInit();
            bmi.UriSource = new Uri(imgUrl, UriKind.RelativeOrAbsolute);
            // 千万级列表必须用OnDemand,否则内存直接溢出
            bmi.CacheOption = BitmapCacheOption.OnDemand;
            // 关键优化:延迟创建图片帧(进一步降低内存)
            bmi.CreateOptions = BitmapCreateOptions.DelayCreation;
            // 禁用自动旋转提升性能
            bmi.Rotation = Rotation.Rotate0;
            bmi.EndInit();
            if(bmi.CanFreeze)
            {
                bmi.Freeze();
            }            

            // 将图片加入缓存
            imageCache.Add(imgUrl, bmi, cachePolicy);
            return bmi;
        }

        /// <summary>
        /// 缓存项被移除时的回调(监控/日志用)
        /// </summary>
        private void CacheItemRemovedCallback(CacheEntryRemovedArguments args)
        {
            System.Diagnostics.Debug.WriteLine($"缓存项被移除:{args.CacheItem.Key},原因:{args.RemovedReason}");
        }


        private ObservableCollection<Book> booksCollection;
        public ObservableCollection<Book> BooksCollection
        {
            get
            {
                return booksCollection;
            }
            set
            {
                SetProperty(ref booksCollection, value);
            }
        }


        private double gridWidth;
        public double GridWidth
        {
            get
            {
                return gridWidth;
            }
            set
            {
                SetProperty(ref gridWidth, value);
            }
        }

        private double gridHeight;
        public double GridHeight
        {
            get
            {
                return gridHeight;
            }
            set
            {
                SetProperty(ref gridHeight, value);
            }
        }

        private string mainTitle = string.Empty;
        public string MainTitle
        {
            get
            {
                return mainTitle; 
            }
            set
            {
                SetProperty(ref mainTitle, value);
            }
        }
    }

    public class Book
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string ISBN { get; set; }
        public ImageSource ImgSource { get; set; }
    }
}

 

posted @ 2025-12-30 23:45  FredGrit  阅读(1)  评论(0)    收藏  举报