批量更新图片目录Exif元数据时触发System.OutOfMemoryException
解决System.Windows.Media.Imaging处理JPEG元数据时的OutOfMemoryException问题
问题根源
你的代码出现内存泄漏并最终抛出OutOfMemoryException,核心原因有三点:
- 使用
BitmapCacheOption.OnLoad强制加载整张位图到内存,处理大量图片时快速耗尽内存(你只是修改元数据,完全不需要加载像素数据); JpegBitmapDecoder作为类成员未被及时释放,它持有的非托管资源(包括文件句柄)无法被GC及时回收,导致FileStream对象持续增长;- 返回的
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
相关产品推荐
相关产品推荐

