Wpf 应用程序冻结 - 大量内存泄漏之谜

Wpf application freezes - massive memory leak puzzle

我有一个 mvvm wpf 应用程序,它一直正常工作,直到它冻结并导致大量内存泄漏的那一刻。 解决方案文件: Google Disk Solution Link

该应用程序使用本地 mdf 文件,使用 Mahhaps 和一些其他参考资料(例如用于显示 gif 的参考资料)

这是导致问题的视图模型中的方法调用,使用 await 分配联系人导致了问题 - 这在另一个视图模型中使用,它没有任何问题,即使在这里它一直工作到某一刻。

public async  void OnLoad()
        {
            IsRefreshEnabled = false;
            IsRefreshProgressActive = true;
            Contacts =await Task.Run(() => _repository.GetContactsAsync()) ;
            IsRefreshEnabled = true;
            IsRefreshProgressActive = false;
        }

这是视图呈现的方式

<DataGrid SelectedItem="{Binding Contact}" AutoGenerateColumns="True" ItemsSource="{Binding Path=Contacts, Mode=TwoWay}" Style="{StaticResource AzureDataGrid}"  x:Name="dataGridCodeBehind"  Margin="10,54,521,0" VerticalAlignment="Top"  HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" ColumnWidth="*">
            <DataGrid.ColumnHeaderStyle>
                <Style TargetType="DataGridColumnHeader">
                    <Setter Property="FontSize" Value="10"/>
                </Style>
            </DataGrid.ColumnHeaderStyle>
        </DataGrid>

我尝试删除 gif 的库(gif 未显示在该视图中,而在另一个视图中没有出现任何问题)。

对存储库的相同调用 - 获取我在另一个视图中的联系人数据,并且没有产生任何问题。

此联系人视图一直运行良好,直到突然之间。

我尝试调试,但调试器甚至没有找到代码。

将一些数据加载到数据库后,我冻结了。

冻结是通过 ContactsViewModel 的 OnLoad 方法进行的 - 此对存储库的调用用于另一个没有任何问题的 ViewModel - 它 returns 数据快速

ContactsViewModel 代码:

using Digital_Data_House_Bulk_Mailer.Commands;
using Digital_Data_House_Bulk_Mailer.Model;
using Digital_Data_House_Bulk_Mailer.Repository;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;

namespace Digital_Data_House_Bulk_Mailer.ViewModel
{
    class ContactsViewModel : INotifyPropertyChanged
    {
        private Contact _contact;

        private IContactRepository _repository = new ContactRepository();

        public event PropertyChangedEventHandler PropertyChanged = delegate { };

        public static event EventHandler<PropertyChangedEventArgs> StaticPropertyChanged;

        private static void NotifyStaticPropertyChanged(string propertyName)
        {
            if (StaticPropertyChanged != null)
                StaticPropertyChanged(null, new PropertyChangedEventArgs(propertyName));
        }

        public RelayCommand UpdateCommand { get; set; }

        public RelayCommand LoadCommand { get; set; }

        public RelayCommand DeleteCommand { get; set; }

        public RelayCommand DeleteAllContactsCommand { get; set; }

        public RelayCommand RecreateFiltersCommand { get; set; }

        private ObservableCollection<Contact> _contacts;

        public ObservableCollection<Contact> Contacts
        {
            get { return _contacts; }
            set
            {
                _contacts = value;
                PropertyChanged(this, new PropertyChangedEventArgs("Contacts"));
                //used in case of static Contacts property
                //NotifyStaticPropertyChanged("Contacts"); 
            }
        }


        public Contact Contact
        {
            get { return _contact; }
            set
            {
                _contact = value;
                DeleteCommand.RaiseCanExecuteChanged();
                UpdateCommand.RaiseCanExecuteChanged();
                PropertyChanged(this, new PropertyChangedEventArgs("Contact"));
            }
        }

        private bool _isRefreshEnabled=true;
        public bool IsRefreshEnabled
        {
            get { return _isRefreshEnabled; }
            set
            {
                _isRefreshEnabled = value;
                PropertyChanged(this, new PropertyChangedEventArgs("IsRefreshEnabled"));
            }
        }

        private bool _isRefreshProgressActive = false;
        public bool IsRefreshProgressActive
        {
            get { return _isRefreshProgressActive; }
            set
            {
                _isRefreshProgressActive = value;
                PropertyChanged(this, new PropertyChangedEventArgs("IsRefreshProgressActive"));
            }
        }

        public ContactsViewModel()
        {
            DeleteCommand = new RelayCommand(OnDelete, CanDelete);
            UpdateCommand = new RelayCommand(OnUpdate, CanUpdate);
            LoadCommand = new RelayCommand(OnLoad, CanLoad);
            DeleteAllContactsCommand = new RelayCommand(OnDeleteAllContacts, CanDeleteAllContacts);
            RecreateFiltersCommand = new RelayCommand(OnRecreateFilters, CanRecreateFilters);
            OnLoad();
        }

        public bool CanRecreateFilters()
        {
            return true;
        }

        public async void OnRecreateFilters()
        {
            IsRefreshProgressActive = true;
            await Task.Run(() => _repository.ResetFilters());
            IsRefreshProgressActive = false;
        }

        public async void OnDeleteAllContacts()
        {
            MessageBoxResult messageBoxResult = System.Windows.MessageBox.Show("Are you sure?", "DELETE ALL EXISTING CONTACTS", System.Windows.MessageBoxButton.YesNo);
            if (messageBoxResult == MessageBoxResult.Yes)
            {
                IsRefreshProgressActive = true;
                await Task.Run(() => _repository.DeleteAllContacts());
                IsRefreshProgressActive = false;
            }
        }

        public bool CanDeleteAllContacts()
        {
            return true;
        }


        private void OnDelete()
        {
            MessageBoxResult messageBoxResult = System.Windows.MessageBox.Show("Are you sure?", "Delete Contact Confirmation", System.Windows.MessageBoxButton.YesNo);
            if (messageBoxResult == MessageBoxResult.Yes)
            {
                _repository.DeleteContactAsync(Contact);
                Contacts.Remove(Contact);
            }
        }

        private bool CanDelete()
        {          
            if (Contact != null )
            {
                return true;
            }
            return false;
        }

        private  void OnUpdate()
        {
             _repository.AddContactAsync(Contact);

        }

        private bool CanUpdate()
        {
            if (Contact != null )
            {
                return true;
            }
            return false;
        }


        public async  void OnLoad()
        {
            IsRefreshEnabled = false;
            IsRefreshProgressActive = true;
            Contacts =await Task.Run(() => _repository.GetContactsAsync()) ;
            IsRefreshEnabled = true;
            IsRefreshProgressActive = false;
        }

        private  ObservableCollection<Contact> GetContactsAsync()
        {
            return _repository.GetContactsAsync();
        }

        public bool CanLoad()
        {
            return true;
        }

    }
}

联系人存储库class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Digital_Data_House_Bulk_Mailer.Model;
using System.Data.Entity;
using System.Collections.ObjectModel;
using System.Data.Entity.Migrations;
using System.Collections;
using System.Data.Entity.Core.Objects;
using System.Data.SqlClient;

namespace Digital_Data_House_Bulk_Mailer.Repository
{
    class ContactRepository : IContactRepository
    {
        digital_datahouse_bulk_mailerEntities db = null;

        public ContactRepository()
        {
            db = new digital_datahouse_bulk_mailerEntities();
        }

        public string SELECT_ALL { get { return "Select All"; } private set { } }


        public  ObservableCollection<StateFilter> GetStates()
        {

            ObservableCollection<StateFilter> stateFilters = new ObservableCollection<StateFilter>();

            foreach (StateFilter state in db.StateFilters.ToList() )
            {
                db.Entry<StateFilter>(state).Reload();
                stateFilters.Add(state);
            }



            return stateFilters;
        }

        public ObservableCollection<CountyFilter> GetCounties()
        {
            ObservableCollection<CountyFilter> countyFilters = new ObservableCollection<CountyFilter>();

            foreach (CountyFilter county in db.CountyFilters.ToList())
            {
                db.Entry<CountyFilter>(county).Reload();
                countyFilters.Add(county);
            }

            return countyFilters;
        }

        public ObservableCollection<GenderFilter> GetGenders()
        {
            ObservableCollection<GenderFilter> genderFilters = new ObservableCollection<GenderFilter>();

            foreach (GenderFilter gender in db.GenderFilters.ToList())
            {
                db.Entry<GenderFilter>(gender).Reload();
                genderFilters.Add(gender);
            }

            return genderFilters;
        }

        public ObservableCollection<IndustryFilter> GetIndustries()
        {
            ObservableCollection<IndustryFilter> industryFilters = new ObservableCollection<IndustryFilter>();

            foreach (IndustryFilter industry in db.IndustryFilters.ToList())
            {
                db.Entry<IndustryFilter>(industry).Reload();
                industryFilters.Add(industry);
            }

            return industryFilters;
        }

        public ObservableCollection<IsContactedFilter> GetIsContacted()
        {
            ObservableCollection<IsContactedFilter> isContactedFilters = new ObservableCollection<IsContactedFilter>();

            foreach (IsContactedFilter isContacted in db.IsContactedFilters.ToList())
            {
                db.Entry<IsContactedFilter>(isContacted).Reload();
                isContactedFilters.Add(isContacted);
            }

            return isContactedFilters;
        }

        public ObservableCollection<SicCodeDescriptionFilter> GetSicCodeDescriptions()
        {
            ObservableCollection<SicCodeDescriptionFilter> sicCodeDescriptionFilters = new ObservableCollection<SicCodeDescriptionFilter>();

            foreach (SicCodeDescriptionFilter sicCodeDescriptionFilter in db.SicCodeDescriptionFilters.ToList())
            {
                db.Entry<SicCodeDescriptionFilter>(sicCodeDescriptionFilter).Reload();
                sicCodeDescriptionFilters.Add(sicCodeDescriptionFilter);
            }

            return sicCodeDescriptionFilters;
        }



        public void AddContactAsync(Contact contact)
        {
            if (contact != null)
            {
                db.Contacts.AddOrUpdate(contact);
                db.SaveChangesAsync();
            }
        }

        public void DeleteContactAsync(Contact contact)
        {
            if (contact != null)
            {
                db.Contacts.Remove(contact);
                db.SaveChangesAsync();
            }
        }

        public void UpdateContactAsync(Contact contact)
        {
            if (contact != null)
            {
                db.Contacts.AddOrUpdate(contact);
                db.SaveChangesAsync();
            }
        }

        public ObservableCollection<Contact> GetContactsAsync()
        {

            db = new digital_datahouse_bulk_mailerEntities();

            ObservableCollection<Contact> contacts = new ObservableCollection<Contact>();

            foreach (var contact in db.Contacts.ToList())
            {
                contacts.Add(contact);
            }
            return contacts;
        }

        public ObservableCollection<Contact> FilterContacts(ObservableCollection<StateFilter> states, ObservableCollection<CountyFilter> counties, 
            ObservableCollection<GenderFilter> genders, ObservableCollection<IndustryFilter> industries, ObservableCollection<IsContactedFilter> contacted, 
            ObservableCollection<SicCodeDescriptionFilter> codes, bool hasWebsite)
        {

            db = new digital_datahouse_bulk_mailerEntities();

            ObservableCollection<Contact> filteredContacts = new ObservableCollection<Contact>();

            string[] stateArray = (from s in states where s.IsChecked==true select s.State).ToArray();
            string[] countyArray= (from c in counties where c.IsChecked==true select c.County).ToArray();
            string[] genderArray= (from g in genders where g.IsChecked==true select g.Gender).ToArray();
            string[] industryArray = (from i in industries where i.IsChecked==true select i.Industry).ToArray();
            string[] contactedArray = (from c in contacted where c.IsChecked==true select c.IsContacted.ToString()).ToArray();
            string[] sicCodeArray = (from c in codes where c.IsChecked==true select c.SicCodeDescription).ToArray();

            var contacts=(from c in db.Contacts
                          where
                          stateArray.Contains(c.State) &&
                          countyArray.Contains(c.County) &&
                          genderArray.Contains(c.Gender) &&
                          industryArray.Contains(c.Industry) &&
                          contactedArray.Contains(c.IsContacted) &&
                          sicCodeArray.Contains(c.SIC_Code_Description)

                          select c).ToList();

            foreach (Contact contact in contacts)
            {
                if(hasWebsite==true)
                {
                    if(!String.IsNullOrEmpty(contact.WebSite))
                    {
                        filteredContacts.Add(contact);
                    }
                }
                else
                {
                    filteredContacts.Add(contact);
                }

            }

            return filteredContacts;
        }

        public void AddContactsRange(ObservableCollection<Contact> contacts)
        {

                db.Contacts.AddRange(contacts);
                db.SaveChanges();

        }

        public void ResetFilters()
        {
            ResetStates();
            ResetCounties();
            ResetIsContactedFilters();
            ResetGenders();
            ResetIndustries();
            ResetSicFilters(); 

        }

        private void ResetStates()
        {
            db.Database.ExecuteSqlCommand("TRUNCATE TABLE [StateFilter]");

            db = new digital_datahouse_bulk_mailerEntities();

            List<StateFilter> stateFilters = new List<StateFilter>();

            var states = (
                from c in db.Contacts
                select c.State

                ).Distinct().ToList();

            foreach (var stateName in states)
            {
                StateFilter state = new StateFilter();
                state.State = stateName;
                state.IsChecked = true;
                stateFilters.Add(state);

            }


            db.StateFilters.AddRange(stateFilters);
            db.SaveChanges();

        }

        public void DeleteAllContacts()
        {
            db.Database.ExecuteSqlCommand("TRUNCATE TABLE [Contact]");           
            db = new digital_datahouse_bulk_mailerEntities();   
        }

        private void ResetCounties()
        {
            db.Database.ExecuteSqlCommand("TRUNCATE TABLE [CountyFilter]");
            db = new digital_datahouse_bulk_mailerEntities();

            List<CountyFilter> countyFilters = new List<CountyFilter>();

            var counties = (
                from c in db.Contacts
                select c.County

                ).Distinct().ToList();

            foreach (var countyName in counties)
            {
                CountyFilter county = new CountyFilter();
                county.County = countyName;
                county.IsChecked = true;
                countyFilters.Add(county);
            }

            db.CountyFilters.AddRange(countyFilters);
            db.SaveChanges();
        }

        private void ResetGenders()
        {
            db.Database.ExecuteSqlCommand("TRUNCATE TABLE [GenderFilter]");
            db = new digital_datahouse_bulk_mailerEntities();

            List<GenderFilter> genderFilters = new List<GenderFilter>();

            var genders = (
                from c in db.Contacts
                select c.Gender

                ).Distinct().ToList();

            foreach (var genderName in genders)
            {
                GenderFilter gender = new GenderFilter();
                gender.Gender = genderName;
                gender.IsChecked = true;
                genderFilters.Add(gender);
            }

            db.GenderFilters.AddRange(genderFilters);
            db.SaveChanges();
        }

        private void ResetIndustries()
        {
            db.Database.ExecuteSqlCommand("TRUNCATE TABLE [IndustryFilter]");
            db = new digital_datahouse_bulk_mailerEntities();

            List<IndustryFilter> industryFilters = new List<IndustryFilter>();

            var industries = (
                from c in db.Contacts
                select c.Industry

                ).Distinct().ToList();

            foreach (var industryName in industries)
            {
                IndustryFilter industry = new IndustryFilter();
                industry.Industry = industryName;
                industry.IsChecked = true;
                industryFilters.Add(industry);
            }

            db.IndustryFilters.AddRange(industryFilters);
            db.SaveChanges();
        }

        private void ResetIsContactedFilters()
        {
            db.Database.ExecuteSqlCommand("TRUNCATE TABLE [IsContactedFilter]");
            db = new digital_datahouse_bulk_mailerEntities();

            List<IsContactedFilter> isContactedFilters = new List<IsContactedFilter>();

            var isContacted = (
                from c in db.Contacts
                select c.IsContacted

                ).Distinct().ToList();

            foreach (var contactedName in isContacted)
            {
                IsContactedFilter contacted = new IsContactedFilter();
                contacted.IsContacted = contactedName;
                contacted.IsChecked = true;
                isContactedFilters.Add(contacted);
            }

            db.IsContactedFilters.AddRange(isContactedFilters);
            db.SaveChanges();
        }

        private void ResetSicFilters()
        {
            db.Database.ExecuteSqlCommand("TRUNCATE TABLE [SicCodeDescriptionFilter]");
            db = new digital_datahouse_bulk_mailerEntities();

            List<SicCodeDescriptionFilter> sicFilters = new List<SicCodeDescriptionFilter>();

            var sics = (
                from c in db.Contacts
                select c.SIC_Code_Description

                ).Distinct().ToList();

            foreach (var sic in sics)
            {
                SicCodeDescriptionFilter sicCode = new SicCodeDescriptionFilter();
                sicCode.SicCodeDescription = sic;
                sicCode.IsChecked = true;
                sicFilters.Add(sicCode);
            }

            db.SicCodeDescriptionFilters.AddRange(sicFilters);
            db.SaveChanges();
        }

        public void UpdateIsContactedInformation(Contact contact)
        {
            db = new digital_datahouse_bulk_mailerEntities();

            contact.IsContacted = "True";
            contact.MessageDateSent = DateTime.Today;

            db.Contacts.AddOrUpdate(contact);
            db.SaveChanges();
        }
    }
}

这是使用名为 ContactsView 的 ViewModel 的视图 - 该视图与其他视图一样包含在 MainWindow 中

联系人视图:

<UserControl x:Class="Digital_Data_House_Bulk_Mailer.View.ContactsView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:Digital_Data_House_Bulk_Mailer.View"
         xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
         xmlns:model="clr-namespace:Digital_Data_House_Bulk_Mailer.ViewModel"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
    <model:ContactsViewModel/>
</UserControl.DataContext>
<Grid Margin="0,0,-690,-36" >
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="61*"/>
        <ColumnDefinition Width="10*"/>
    </Grid.ColumnDefinitions>
    <Button  Style="{StaticResource AccentedSquareButtonStyle}" Command="{Binding DeleteCommand}" Controls:TextBoxHelper.ClearTextButton="True" x:Name="delete" Content="DELETE" HorizontalAlignment="Left" Margin="115,21,0,0" VerticalAlignment="Top"  Width="100" RenderTransformOrigin="0.267,0.519"/>
    <Button  Style="{StaticResource AccentedSquareButtonStyle}" Command="{Binding UpdateCommand}" Controls:TextBoxHelper.ClearTextButton="True" x:Name="update" Content="add / update" HorizontalAlignment="Left" Margin="10,21,0,0" VerticalAlignment="Top"  Width="100" RenderTransformOrigin="0.267,0.519"/>
    <Button  Style="{StaticResource AccentedSquareButtonStyle}"  Controls:TextBoxHelper.ClearTextButton="True" x:Name="bulk_import" Content="import new data" HorizontalAlignment="Left" Margin="220,21,0,0" VerticalAlignment="Top"  Width="110" RenderTransformOrigin="0.267,0.519" Click="bulk_import_Click"/>
    <DataGrid SelectedItem="{Binding Contact}" AutoGenerateColumns="True" ItemsSource="{Binding Path=Contacts, Mode=TwoWay}" Style="{StaticResource AzureDataGrid}"  x:Name="dataGridCodeBehind"  Margin="10,54,521,0" VerticalAlignment="Top"  HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" ColumnWidth="*">
        <DataGrid.ColumnHeaderStyle>
            <Style TargetType="DataGridColumnHeader">
                <Setter Property="FontSize" Value="10"/>
            </Style>
        </DataGrid.ColumnHeaderStyle>
    </DataGrid>
    <Button IsEnabled="{Binding IsRefreshEnabled}" Style="{StaticResource AccentedSquareButtonStyle}" Command="{Binding LoadCommand}" x:Name="refreshData" Content="Refresh Contacts" HorizontalAlignment="Left" Height="22" Margin="335,21,0,0" VerticalAlignment="Top" Width="114"/>
    <Controls:ProgressRing IsActive="{Binding IsRefreshProgressActive}" Foreground="{DynamicResource AccentColorBrush}" Margin="720,0,0,0" RenderTransformOrigin="-2.889,0.463" HorizontalAlignment="Left" VerticalAlignment="Top" Height="50" Width="50"/>
    <Button x:Name="deleteContacts" Command="{Binding DeleteAllContactsCommand}" Style="{StaticResource AccentedSquareButtonStyle}" Content="Delete All Contacts" HorizontalAlignment="Left" Height="22" Margin="454,21,0,0" VerticalAlignment="Top" Width="114"/>
    <Button x:Name="recreateFilters" Command="{Binding RecreateFiltersCommand}" Content="Recreate TO MAIL Filters" HorizontalAlignment="Left" Style="{StaticResource AccentedSquareButtonStyle}" Height="22" Margin="573,21,0,0" VerticalAlignment="Top" Width="142"/>

</Grid>

非常糟糕的是,当我尝试调试时,它甚至没有转到代码 - 方法调用。 也尝试使用一些分析工具,但我没有得出任何结论是什么导致了这个冻结问题...

当我 运行 从数据库编辑器查询数据库时,我立即得到结果。

我还在使用 ReportViewer 显示的应用程序中使用 SSRS 报告 - 它也可以正常工作 returns 相同的数据 - 它显示在单独的视图中。

在其中一个视图中,我使用 WpfAnimatedGif 库显示 GIF 动画 - 我试图删除此引用以查看是否是问题所在,但它并没有继续冻结...

我还尝试重写我的存储库 class 以使用 using 命令为每个方法创建新的数据库实例,但这不是问题所在。

解决方案文件: Google Disk Solution Link

我终于解决了这个问题。我需要的是将 Height="500" 属性 添加到数据网格。

删除高度 属性 会使 DataGrid 在我的案例中显示超过 50 行时冻结。 添加高度后,它可以在不到一秒的时间内加载 5000 条记录。

只是一个建议。

我正在使用带有大列表的网格。我建议您打开虚拟化,这样可以进一步提高 UI.

的性能

你可以参考这个link了解更多信息

https://docs.microsoft.com/en-us/dotnet/framework/wpf/getting-started/whats-new#new-features-for-the-virtualizingpanel