The below use of BitmapDecoder, BitmapPropertySet, and BitmapTypedValue causes a leak in native memory. I'm hoping I'm just using them incorrectly... What is the correct way to use them? They don't implement IDisposable, so I'm not sure how to tell them to release the native memory they own.
There's a chance this leak cannot be avoided. If so, how would I shield my UWP app from its affects?
A Valloc trace from Windows Performance Recorder/Analyzer shows many allocations with no Decommit Time. Commit stacks for these objects indicate they are related to fetching metadata or are pieces of metadata.
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using Windows.Graphics.Imaging;
using Windows.Storage;
using Windows.Storage.Search;
namespace Tests
{
[TestClass]
public class TestBitmapDecoderLeak
{
// Path to a folder containing jpeg's that have metadata.
private static readonly string ImageFolderPath = @"path to folder of jpegs";
// The subset of the metadata you want to get
private static readonly string [] MetadataPolicies =
{
"System.Copyright",
"System.SimpleRating",
"System.Photo.ShutterSpeed",
"System.Photo.Aperture",
"System.Photo.CameraModel",
"System.Photo.CameraManufacturer",
"System.Photo.DateTaken",
"System.Photo.ExposureTime",
"System.Photo.Flash",
"System.Photo.FlashEnergy",
"System.Photo.ISOSpeed",
"System.GPS.Longitude",
"System.GPS.Latitude"
};
[TestMethod]
public async Task RunTest()
{
// Start your profiler
Debugger.Break();
var imageFiles = await GetJpegFiles();
// Get some metadata for each image, but do nothing with it.
foreach (var imageFile in imageFiles)
{
using (var fileStream = await imageFile.OpenReadAsync())
{
var decoder = await BitmapDecoder.CreateAsync(fileStream);
var DpiX = decoder.DpiX;
var DpiY = decoder.DpiY;
var PixelSizeWidth = decoder.OrientedPixelWidth;
var PixelSizeHeight = decoder.OrientedPixelHeight;
var DecoderId = decoder.DecoderInformation.CodecId;
var properties = await decoder.BitmapProperties.GetPropertiesAsync(MetadataPolicies);
var ShutterSpeed = NullOrValType<double>(properties, "System.Photo.ShutterSpeed");
var Copyright = NullOrRefType<string>(properties, "System.Copyright");
var SimpleRating = NullOrValType<UInt16>(properties, "System.SimpleRating");
var Aperture = NullOrValType<double>(properties, "System.Photo.Aperture");
var CameraModel = NullOrRefType<string>(properties, "System.Photo.CameraModel");
var DateTaken = NullOrValType<DateTimeOffset>(properties, "System.Photo.DateTaken");
var ExposureTime = NullOrValType<double>(properties, "System.Photo.ExposureTime");
var Flash = NullOrValType<UInt16>(properties, "System.Photo.Flash");
var FlashEnergy = NullOrValType<double>(properties, "System.Photo.FlashEnergy");
var IsoSpeed = NullOrValType<UInt16>(properties, "System.Photo.ISOSpeed");
var Longitude = NullOrRefType<double[]>(properties, "System.GPS.Longitude");
var Latitude = NullOrRefType<double[]>(properties, "System.GPS.Latitude");
}
}
// Remove these so they don't add noise to the trace
imageFiles = null;
// Ensure everything is cleaned up to the best of the GC's abilities
GC.Collect();
GC.WaitForPendingFinalizers();
// Stop your profiler
Debugger.Break();
}
private static async Task<IEnumerable<StorageFile>> GetJpegFiles()
{
var folder = await StorageFolder.GetFolderFromPathAsync(ImageFolderPath);
var queryOptions = new QueryOptions
{
FolderDepth = FolderDepth.Deep,
IndexerOption = IndexerOption.DoNotUseIndexer,
};
queryOptions.FileTypeFilter.Add(".jpg");
queryOptions.FileTypeFilter.Add(".jpeg");
queryOptions.FileTypeFilter.Add(".jpe");
var folderQuery = folder.CreateFileQueryWithOptions(queryOptions);
return await folderQuery.GetFilesAsync();
}
private static T NullOrRefType<T>(IDictionary<string, BitmapTypedValue> properties, string policy)
where T : class
{
if (properties == null || !properties.ContainsKey(policy))
return null;
return (T)properties[policy].Value;
}
private static T? NullOrValType<T>(IDictionary<string, BitmapTypedValue> properties, string policy)
where T : struct
{
if (properties == null || !properties.ContainsKey(policy))
return null;
return (T)properties[policy].Value;
}
}
}
I'm not 100% sure if this will help with the memory issues but you can access the file's properties from the StorageFile itself without having to load into the decoder.
You'd do that like this:
var systemProperties = new List<string>{ "System.Photo.ShutterSpeed" };
var props = await file.Properties.RetrievePropertiesAsync(systemProperties);
This will return you a Dictionary containing the system properties that you pass through. You can also pass null into the RetrievePropertiesAsync method and it will return all properties so you can filter from there too.
Then you would extract from the collection possibly like this depending on how you want to use the value:
if (props.ContainsKey("System.Photo.ShutterSpeed"))
{
var cameraShutterSpeedObj = props["System.Photo.ShutterSpeed"];
if (cameraShutterSpeedObj != null)
{
var cameraShutterSpeed = cameraShutterSpeedObj.ToString();
}
}
Give that a try and see if you see any improvement.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With