You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

批量更新图片目录Exif元数据时触发System.OutOfMemoryException

解决System.Windows.Media.Imaging处理JPEG元数据时的OutOfMemoryException问题

问题根源

你的代码出现内存泄漏并最终抛出OutOfMemoryException,核心原因有三点:

  1. 使用BitmapCacheOption.OnLoad强制加载整张位图到内存,处理大量图片时快速耗尽内存(你只是修改元数据,完全不需要加载像素数据);
  2. JpegBitmapDecoder作为类成员未被及时释放,它持有的非托管资源(包括文件句柄)无法被GC及时回收,导致FileStream对象持续增长;
  3. 返回的BitmapFrame可能间接持有Decoder或Stream的引用,进一步阻碍资源释放。

修复代码

1. 修改getBitmapFrame方法

private BitmapFrame getBitmapFrame(string path)
{
    using (Stream stream = File.Open(path, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
    {
        // 改用None模式,仅加载元数据,不缓存像素
        using (var decoder = new JpegBitmapDecoder(stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None))
        {
            // 克隆Frame,切断与Decoder的引用关联
            return decoder.Frames[0].Clone();
        }
    }
}

2. 让JpegMetadataAdapter实现IDisposable

确保处理完图片后能主动释放资源:

public class JpegMetadataAdapter : IDisposable
{
    private string path;
    private BitmapFrame frame;
    public BitmapMetadata Metadata { get; private set; }
    private bool disposed = false;

    public JpegMetadataAdapter(string path)
    {
        this.path = path;
        frame = getBitmapFrame(path);
        Metadata = (BitmapMetadata)frame.Metadata.Clone();
    }

    private BitmapFrame getBitmapFrame(string path)
    {
        // 上面修改后的代码
    }

    public void Save()
    {
        using (Stream stream = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.None))
        {
            var encoder = new JpegBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create(frame, frame.Thumbnail, Metadata, frame.ColorContexts));
            encoder.Save(stream);
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposed) return;

        if (disposing)
        {
            // 释放托管资源
            if (frame is IDisposable disposableFrame)
            {
                disposableFrame.Dispose();
            }
            frame = null;
            Metadata = null;
        }

        disposed = true;
    }

    ~JpegMetadataAdapter()
    {
        Dispose(false);
    }
}

3. 循环处理时使用using包裹

确保每个JpegMetadataAdapter对象用完就释放:

foreach (var fullPath in Directory.EnumerateFiles(imageFolder, "*.jpg"))
{
    using (var jpeg = new JpegMetadataAdapter(fullPath))
    {
        var newKeyWords = new ReadOnlyCollection<string>(new List<string> { "你的关键词列表" });
        jpeg.Metadata.Keywords = newKeyWords;
        jpeg.Save();
    }
}

关键优化点说明

  • BitmapCacheOption.None:仅读取图片的元数据部分,不加载像素数据,内存占用会大幅降低;
  • using包裹Decoder:JpegBitmapDecoder实现了IDisposable,using能确保它的非托管资源(包括文件句柄)被及时释放;
  • 克隆BitmapFrame:切断Frame与原始Decoder的引用关联,避免Decoder被Frame持有而无法回收;
  • 实现IDisposable:主动释放BitmapFrame和Metadata资源,避免GC延迟回收导致的内存堆积。

内容的提问来源于stack exchange,提问作者shane

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 10:06:22