如何在不导致本机内存泄漏的情况下访问图像的元数据?

Tri*_*tan 7 c# performance memory-leaks windows-10 uwp

以下使用BitmapDecoder,BitmapPropertySet和BitmapTypedValue会导致本机内存泄漏.我希望我只是错误地使用它们...使用它们的正确方法是什么?它们没有实现IDisposable,所以我不知道如何告诉他们释放他们拥有的本机内存.

这种泄漏有可能无法避免.如果是这样,我将如何保护我的UWP应用程序免受其影响?

Windows性能记录器/分析器的Valloc跟踪显示许多分配,没有Decommit Time.这些对象的提交堆栈表明它们与获取元数据或元数据相关.

WPR-WPA Valloc运行以下测试代码的跟踪

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Jam*_*oft 1

我不确定这是否有助于解决内存问题,但您可以从 StorageFile 本身访问文件的属性,而无需加载到解码器中。

你会这样做:

var systemProperties = new List<string>{ "System.Photo.ShutterSpeed" };
var props = await file.Properties.RetrievePropertiesAsync(systemProperties);
Run Code Online (Sandbox Code Playgroud)

这将返回一个包含您传递的系统属性的字典。您还可以将 null 传递到 RetrievePropertiesAsync 方法,它将返回所有属性,以便您也可以从那里进行筛选。

然后,您可能会像这样从集合中提取数据,具体取决于您想要如何使用该值:

if (props.ContainsKey("System.Photo.ShutterSpeed"))
{
    var cameraShutterSpeedObj = props["System.Photo.ShutterSpeed"];
    if (cameraShutterSpeedObj != null)
    {
        var cameraShutterSpeed = cameraShutterSpeedObj.ToString();
    }
}
Run Code Online (Sandbox Code Playgroud)

尝试一下,看看是否有任何改进。