图像编辑和保存超出内存异常C#

时间:2016-07-19 10:52:36

标签: c# wpf memory-leaks out-of-memory

我在WPF应用程序中工作,我在两个地方显示我的图像,这意味着相同的图像被加载到两个地方。在其中一个位置,图像将与滑块中的其他几个图像一起显示,以便能够编辑和保存。如果该位置没有可用的图像,我应该显示一个单独的图像,找不到不可编辑的图像。

当我开始处理该功能时,我在编辑和保存期间获得了另一个进程异常。所以在搜索之后我想出了一个解决方案,现在我很难在单击Next或Previous或First或Last in滑块时出现内存不足异常。滑块只是一个带有4个按钮的Image控件。单击按钮时,将调用以下方法。我不确定是否有任何内存泄漏。

bool NoImage = true;
private static readonly object _syncRoot = new object();
private BitmapSource LoadImage(string path)
{
    lock (_syncRoot) //lock the object so it doesn't get executed more than once at a time.
    {
        BitmapDecoder decoder = null;

        try
        {
            //If the image is not found in the folder, then show the image not found.
            if (!File.Exists(path) && (path != null))
            {
                System.Drawing.Bitmap ss = XXXX.Resources.ImageNotFound;
                var stream = new System.IO.MemoryStream();
                if (!File.Exists(Path.GetTempPath() + "ImageNotFound.jpg"))
                {
                    FileStream file = new FileStream(Path.GetTempPath() + "ImageNotFound.jpg", FileMode.Create, FileAccess.Write);
                    ss.Save(stream, ImageFormat.Jpeg);
                    stream.Position = 0;
                    stream.WriteTo(file);
                    file.Close();
                    stream.Close();
                }

                path = Path.Combine(Path.GetTempPath(), "ImageNotFound.jpg");
                NoImage = false;
            }
            else
            {
                if (!EnableForEdit)
                    NoImage = false;
                else
                    NoImage = true;
            }

            if (!string.IsNullOrEmpty(path) && (!NoImage || File.Exists(path)))
            {
                using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read))
                {
                    decoder = BitmapDecoder.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);

                }
                return decoder.Frames.FirstOrDefault();

            }
            else
                return null;
        }
        catch (OutOfMemoryException ex)
        {
            MessageBox.Show("Insufficient memory to handle the process. Please try again later.", "Application alert");                  

            return null;
        }
        catch (Exception ex)
        {
            // Error handling.
            throw new ApplicationException(ex.Message);
        }
        finally
        {
            decoder = null;
            GC.WaitForFullGCComplete(1000);
            GC.Collect(0, GCCollectionMode.Forced);
        }
    }
}


<Image x:Name="viewImage" Grid.Row="2" Height="100" Width="135" Source="{Binding DisplayImage, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}" />

如果我的方法有误,请告诉我应该在哪里进行更改,或者是否有更简单的方法。请帮助。

注意:加载的图片高于5Mb

1 个答案:

答案 0 :(得分:4)

首先,当你创建一个流时,你需要在完成它后处理它(注意关闭不处理,但Dispose确实关闭),如果没有,则流停留在内存中消耗资源

因此您的代码应如下所示

using(var stream = new System.IO.MemoryStream())
{
    if (!File.Exists(Path.GetTempPath() + "ImageNotFound.jpg"))
    {
        using(FileStream file = new FileStream(Path.GetTempPath() + "ImageNotFound.jpg", FileMode.Create, FileAccess.Write))
        {
            ss.Save(stream, ImageFormat.Jpeg);
            stream.Position = 0;
            stream.WriteTo(file);
        }
    }
}

其次,您需要减少应用内存的影响

要做到这一点,我建议利用WPF中已有的功能,这是一个如何做到这一点的快速示例

你的模特

public class ImageItem
{
    public Uri URI{ get; set; }

    private BitmapSource _Source;

    public BitmapSource Source
    {
        get
        {
            try
            {
                if (_Source == null) _Source = new BitmapImage(URI);//lazy loading

            }
            catch (Exception)
            {
                _Source = null;
            }
            return _Source;
        }
    }
    public void Save(string filename)
    {
        var img = BitmapFrame.Create(Source);
        var encoder = new JpegBitmapEncoder();

        encoder.Frames.Add(img);
        using(var saveStream = System.IO.File.OpenWrite(filename))
            encoder.Save(saveStream)

    }


}

您的视图模型

public class ImageList : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public ObservableCollection<ImageItem> Images { get; } = new ObservableCollection<ImageItem>();

    private int _SelectedIndex;
//  c# >= 6
    public static readonly PropertyChangedEventArgs SelectedIndexProperty = new PropertyChangedEventArgs(nameof(SelectedIndex));
//  c# < 6
//  public static readonly PropertyChangedEventArgs SelectedIndexProperty = new PropertyChangedEventArgs("SelectedIndex");

    public int SelectedIndex
    {
        get { return _SelectedIndex; }
        set
        {
            _SelectedIndex = value;
//          c# >= 6
            PropertyChanged?.Invoke(this, SelectedIndexProperty);
            PropertyChanged?.Invoke(this, CurrentImageProperty);
//          c# < 6
//          var handler = PropertyChanged;
//          if(handler !=null)
//          {
//              handler (this, SelectedIndexProperty);
//              handler (this, CurrentImageProperty);
//          }
        }
    }

//  c# >= 6
    public static readonly PropertyChangedEventArgs CurrentImageProperty = new PropertyChangedEventArgs(nameof(CurrentImage)); 
//  c# < 6
//  public static readonly PropertyChangedEventArgs CurrentImageProperty = new PropertyChangedEventArgs("CurrentImage"); 

    public ImageItem CurrentImage => Images.Count>0 ?  Images[SelectedIndex] : null;

    public void Next()
    {
        if (SelectedIndex < Images.Count - 1)
            SelectedIndex++;
        else
            SelectedIndex = 0;
    }
    public void Back()
    {
        if (SelectedIndex == 0)
            SelectedIndex = Images.Count - 1;
        else
            SelectedIndex--;
    }

    public void Add(string Filename)
    {
        Images.Add(new ImageItem() { URI= new Uri(Filename) });
//      c# >= 6
        PropertyChanged?.Invoke(this, CurrentImageProperty);
//      c# < 6
//      var handler = PropertyChanged;
//      if(handler !=null)
//      {
//          handler (this, CurrentImageProperty);
//      }
    }
}

最后你的观点

<Window x:Class="ImageDemo.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:ImageDemo"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <BitmapImage x:Key="NotFound" UriSource="C:\...\NotFound.png"/>
    </Window.Resources>
    <Window.DataContext>
        <local:ImageList/>
    </Window.DataContext>
    <DockPanel>
        <Button Content="&lt;" Click="Back_Click"/>
        <Button DockPanel.Dock="Right" Content="&gt;" Click="Next_Click"/>
        <Image Source="{Binding CurrentImage.Source, Mode=OneWay, 
               TargetNullValue={StaticResource NotFound},
               FallbackValue={StaticResource NotFound}}"/>
    </DockPanel>
</Window>

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    //c# >= 6
    private ImageList list => DataContext as ImageList;
    //c# < 6
    //private ImageList list {get{ return DataContext as ImageList;}}

    private void Next_Click(object sender, RoutedEventArgs e)
    {
        list.Next();
    }

    private void Back_Click(object sender, RoutedEventArgs e)
    {
        list.Back();
    }
}

请注意: 因为模型与视图是分开的,所以您可以在几个地方显示相同的图像而不会出现任何问题

System.Drawing.Bitmap也不兼容WPF,因此您应该使用System.Windows.Media.Imaging中的WPF类