【问题标题】:How to free memory after set Image's Source with BitmapImage?使用 BitmapImage 设置 Image 的 Source 后如何释放内存?
【发布时间】:2014-11-20 11:11:13
【问题描述】:

我在 WPF 中有一个用户控件,它包含一个 Image 并且它的 Source 与 ImgSource 绑定:

    public ImageWithWatingCtl()
    {
        this.DataContext = this;
        InitializeComponent();
        story = (base.Resources["waiting"] as Storyboard);
        StartLoading();
    }
    private ImageSource imgSource;

    public ImageSource ImgSource
    {
        get { return imgSource; }
        set
        {
            if (imgSource != null)
            { 
                imgSource = null;
            }
            imgSource = value;
            this.Dispatcher.BeginInvoke(new Action(() => { StopLoading(); }));
            OnPropertyChanged("ImgSource");
        }
    }


    public string Source
    {
        get { return (string)GetValue(SourceProperty); }
        set { SetValue(SourceProperty, value); }
    }

    public static readonly DependencyProperty SourceProperty =
        DependencyProperty.Register("Source", typeof(string), typeof(ImageWithWatingCtl), new FrameworkPropertyMetadata(new PropertyChangedCallback(CallBack)));

    private static void CallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ImageWithWatingCtl imgCtl = d as ImageWithWatingCtl;
        imgCtl.path = e.NewValue != null ? e.NewValue.ToString() : "";
        imgCtl.GetImage();
    }

    private void GetImage()
    {
        if (!string.IsNullOrEmpty(path))
        {

            service.OnComplate += service_OnComplate;
            service.EnQueue(path);

        }
    }

    void service_OnComplate(string url, BitmapImage bitmap)
    {
        if (url == path)
        {

            if (bitmap != null)
            {
                if (ImgSource == null)
                    ImgSource = bitmap;

            }

        }
    }

    private void StartLoading()
    {
        this.story.Begin();
        imageLoading.Visibility = Visibility.Visible;
    }
    private void StopLoading()
    {
        this.story.Stop();
        imageLoading.Visibility = Visibility.Collapsed;
    }

此控件获取一个 url 并将其发送到 GetImageService

    public delegate void ComplateDelegate(string url, BitmapImage bitmap);
    public event ComplateDelegate OnComplate;
    private List<string> LstImageInfo { get; set; }
    object lstlock = new object();

    private static GetImageService instance = null;
    private readonly static object instance_lock = new object();

    public static GetImageService GetInstance()
    {
        if (instance == null)
        {
            lock (instance_lock)
            {
                if (instance == null)
                {
                    instance = new GetImageService();
                }
            }
        }
        return instance;
    }

    private GetImageService()
    {
        LstImageInfo = new List<string>();
        Thread getTread = new Thread(new ThreadStart(GetImage));
        getTread.IsBackground = true;
        getTread.Start();
    }

    public void EnQueue(string info)
    {
        lock (lstlock)
        {
            if (!LstImageInfo.Contains(info))
                LstImageInfo.Add(info);
        }
    }

    private void GetImage()
    {
        while (true)
        {
            lock (lstlock)
            {
                if (LstImageInfo.Count > 0)
                {
                    Console.WriteLine(LstImageInfo.Count);
                    BitmapImage bitmap = null;
                    var info = LstImageInfo[0];
                    if (info.StartsWith("http:"))
                    {
                        bitmap = ShareClass.GetBitemapUrl(info);
                    }
                    else
                    {
                        bitmap = ShareClass.GetBitmapImage(info);
                    }
                    if (bitmap.CanFreeze)
                        bitmap.Freeze();
                    if (OnComplate != null)
                        OnComplate(info, bitmap);
                    LstImageInfo.RemoveAt(0);
                }
            }
            Thread.Sleep(100);
        }
    }

用于获取图片的分享类:

    public static BitmapImage GetBitmapImage(string path, int imageWidth = 0)
    {
        BitmapImage bitmap;
        if (!File.Exists(path))
        {
            path = Path.Combine(GetAssemblyPath(), path);
        }
        if (File.Exists(path))
        {
            using (MemoryStream ms = new MemoryStream(File.ReadAllBytes(path)))
            {
                bitmap = new BitmapImage();
                bitmap.BeginInit();

                bitmap.CacheOption = BitmapCacheOption.OnLoad;
                if (imageWidth > 0)
                {
                    using (System.Drawing.Image drawingImage = System.Drawing.Image.FromStream(ms))
                    {
                        int old_w = drawingImage.Width;
                        int old_h = drawingImage.Height;
                        int imageHeight = (int)(old_h * (imageWidth * 1.0 / old_w));
                        using (System.Drawing.Image thumImage = drawingImage.GetThumbnailImage(imageWidth, imageHeight, () => { return true; }, IntPtr.Zero))
                        {
                            MemoryStream ms_thum = new MemoryStream();
                            thumImage.Save(ms_thum, System.Drawing.Imaging.ImageFormat.Png);
                            bitmap.StreamSource = ms_thum;
                        }
                    }
                }
                else
                {
                    bitmap.StreamSource = ms;
                }
                bitmap.EndInit();
                bitmap.Freeze();
            }

            return bitmap;
        }
        else
            return null;
    }
     public static BitmapImage GetBitemapUrl(string url)
    {
        try
        {
            if (string.IsNullOrEmpty(url))
            {
                return null;
            }
            else
            {
                BitmapImage bitmap = new BitmapImage();
                WebClient wc = new WebClient();
                using (var ms = new MemoryStream(wc.DownloadData(url)))
                {
                    bitmap = new BitmapImage();
                    bitmap.BeginInit();
                    bitmap.CacheOption = BitmapCacheOption.OnLoad;
                    bitmap.StreamSource = ms;
                    bitmap.EndInit();
                    if (bitmap.CanFreeze)
                        bitmap.Freeze();
                }

                return bitmap;
            }
        }
        catch (Exception ex)
        {
            return null;
        }
    }

我尝试了很多方法,设置ImgSource =null,使用GC,但是没有用,那么如何释放内存或者有其他方法来实现我的需求?

顺便说一句,实际上GetImageService中的图片都是20~30kb,但是如果我设置ImgSource = bitmap,我发现内存增加了10mb以上。

【问题讨论】:

  • 只是出于好奇,您是在实现某种动画效果吗?
  • @kennyzx 是的,在我的图像用户控件中,我创建了一个故事板来显示加载效果,in service_OnComplate() 我停止它:this.Dispatcher.BeginInvoke(new Action(() =&gt; { StopLoading(); }));

标签: c# wpf


【解决方案1】:

首先,您应该确保删除了使用 ImageSource 的 UI 控件。 GC.Collect 应该很好用,如果 GC.Collect 无法释放内存,至少应该有一个对 ImageSource 的引用。

在你的情况下,WPF UI 就像一棵树。

UserControl -> BitmapImage -> ImageSource

UserControl 和 BitmapImage 都有 Childen、VisualChildren 和 LogicChildren,都可以引用你的 ImageSource。

【讨论】:

    【解决方案2】:

    关于这个主题有很多SO线程,这个问题的一些解决方案是

    1. Freeze the BitmapImage

    原因:[从链接复制]

    触发此泄漏是因为 WPF 在后台保持静态 BitmapImage 和 Image 之间的强引用。

    修复/解决方法是冻结 BitmapImage。

    BitmapImage bitmap = ShareClass.GetBitemapUrl(info);
    bitmap.Freeze(); 
    OnComplate(info, bitmap);
    

    2. Load image from stream instead of Uri.

    您可以验证它们是否可以解决内存问题。

    【讨论】:

    • 我已经在 ShareClass 中冻结了位图图像,此外我在 OnComplate 之前再次冻结了它,但没有用。请看我的更新
    猜你喜欢
    • 1970-01-01
    • 2012-10-10
    • 1970-01-01
    • 2012-02-28
    • 2013-03-13
    • 1970-01-01
    • 2014-03-19
    • 1970-01-01
    相关资源
    最近更新 更多