programing

WPF가 GC를 호출하는 것을 피할 수 있는 모든 방법.반사 외에 수집(2)?

testmans 2023. 5. 20. 10:31
반응형

WPF가 GC를 호출하는 것을 피할 수 있는 모든 방법.반사 외에 수집(2)?

최근 WPF 클래스의 개인 필드를 조작하기 위해 프로덕션 코드에 이 괴물을 체크인해야 했습니다. (tl;dr 어떻게 하면 이렇게 하지 않을 수 있습니까?)

private static class MemoryPressurePatcher
{
    private static Timer gcResetTimer;
    private static Stopwatch collectionTimer;
    private static Stopwatch allocationTimer;
    private static object lockObject;

    public static void Patch()
    {
        Type memoryPressureType = typeof(Duration).Assembly.GetType("MS.Internal.MemoryPressure");
        if (memoryPressureType != null)
        {
            collectionTimer = memoryPressureType.GetField("_collectionTimer", BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null) as Stopwatch;
            allocationTimer = memoryPressureType.GetField("_allocationTimer", BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null) as Stopwatch;
            lockObject = memoryPressureType.GetField("lockObj", BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null);

            if (collectionTimer != null && allocationTimer != null && lockObject != null)
            {
                gcResetTimer = new Timer(ResetTimer);
                gcResetTimer.Change(TimeSpan.Zero, TimeSpan.FromMilliseconds(500));
            }
        }                
    }       

    private static void ResetTimer(object o)
    {
        lock (lockObject)
        {
            collectionTimer.Reset();
            allocationTimer.Reset();
        }
    }
}

제가 왜 그렇게 미친 짓을 하는지 이해하려면 다음을 살펴봐야 합니다.

/// <summary>
/// Check the timers and decide if enough time has elapsed to
/// force a collection
/// </summary>
private static void ProcessAdd()
{
    bool shouldCollect = false;

    if (_totalMemory >= INITIAL_THRESHOLD)
    {
        // need to synchronize access to the timers, both for the integrity
        // of the elapsed time and to ensure they are reset and started
        // properly
        lock (lockObj)
        {
            // if it's been long enough since the last allocation
            // or too long since the last forced collection, collect
            if (_allocationTimer.ElapsedMilliseconds >= INTER_ALLOCATION_THRESHOLD
                || (_collectionTimer.ElapsedMilliseconds > MAX_TIME_BETWEEN_COLLECTIONS))
            {
                _collectionTimer.Reset();
                _collectionTimer.Start();

                shouldCollect = true;
            }
            _allocationTimer.Reset();
            _allocationTimer.Start();
        }

        // now that we're out of the lock do the collection
        if (shouldCollect)
        {
            Collect();
        }
    }

    return;
}

이 을 중한비메호를출끝있하에다습니는서드트요라고 부릅니다.Collect():

private static void Collect()
{
    // for now only force Gen 2 GCs to ensure we clean up memory
    // These will be forced infrequently and the memory we're tracking
    // is very long lived so it's ok
    GC.Collect(2);
}

네, WPF는 실제로 GC를 완전히 차단하는 GC를 강제로 2세대 가비지 수집을 강제합니다.자연적으로 발생하는 GC는 gen 2 힙에서 차단하지 않고 발생합니다.이것이 실제로 의미하는 것은 이 메서드가 호출될 때마다 우리의 전체 앱이 잠깁니다.당신의 앱이 더 많은 메모리를 사용하고 있고, 당신의 2세대 힙이 더 많이 조각날수록, 그것은 더 오래 걸릴 것입니다.현재 우리 앱은 상당한 양의 데이터를 캐시하고 1기가바이트의 메모리를 쉽게 차지할 수 있으며 강제 GC는 매 850MS마다 몇 초 동안 느린 장치에서 앱을 잠글 수 있습니다.

반대로 저자의 항의에도 불구하고, 이 방법이 매우 빈번하게 호출되는 시나리오에 도달하기 쉽기 때문입니다.WPF의 이 메모리 코드는 파일에서 를 로드할 때 발생합니다.디스크에 저장된 미리 보기로 각 항목을 나타내는 수천 개의 항목으로 목록 보기를 가상화합니다.아래로 스크롤하면 섬네일에 동적으로 로드됩니다. 그리고 GC는 최대 주파수로 발생합니다.따라서 앱이 지속적으로 잠기면서 스크롤이 믿을 수 없을 정도로 느리고 빠릅니다.

위에서 언급한 끔찍한 반사 해킹으로 우리는 타이머가 절대 충족되지 않도록 강제하고, 따라서 WPF는 GC를 강제하지 않습니다.게다가, 부정적인 결과는 없는 것으로 보입니다. 메모리는 스크롤됨에 따라 증가하고 결국 GC는 메인 스레드를 잠그지 않고 자연스럽게 트리거됩니다.

그 전화들을 막을 다른 방법이 있습니까?GC.Collect(2)그게 내 해결책만큼 끔찍하지도 않소?이 해킹을 수행함으로써 발생할 수 있는 구체적인 문제가 무엇인지 설명을 듣고 싶습니다.즉, 전화를 피하는 데 문제가 있다는 뜻입니다.GC.Collect(2)하는 GC는 충분해야

주의: 앱에 병목 현상이 발생하는 경우에만 이 작업을 수행하고, 그 결과를 이해했는지 확인하십시오. 애초에 WPF에 이 작업을 적용한 이유에 대한 자세한 설명은 Hans의 답변을 참조하십시오.

프레임워크에 있는 악성 해킹을 고치려고 하는 악성 코드가 있습니다.모든 것이 정적이고 WPF의 여러 위치에서 호출되므로 반사를 사용하여 중단하는 것보다 더 나은 방법은 없습니다(다른 솔루션이 훨씬 더 좋지 않을 수 있습니다).

그러니 거기서 깨끗한 해결책을 기대하지 마세요.그들이 WPF 코드를 변경하지 않는 한 그러한 것은 존재하지 않습니다.

하지만 저는 당신의 해킹이 더 간단하고 타이머를 사용하지 않을 수 있다고 생각합니다: 그냥 해킹하세요._totalMemory가치만 있다면 당신은 끝입니다.그것은long그 말은 음의 값으로 갈 수 있다는 뜻입니다.그리고 그것은 매우 큰 부정적인 가치입니다.

private static class MemoryPressurePatcher
{
    public static void Patch()
    {
        var memoryPressureType = typeof(Duration).Assembly.GetType("MS.Internal.MemoryPressure");
        var totalMemoryField = memoryPressureType?.GetField("_totalMemory", BindingFlags.Static | BindingFlags.NonPublic);

        if (totalMemoryField?.FieldType != typeof(long))
            return;

        var currentValue = (long) totalMemoryField.GetValue(null);

        if (currentValue >= 0)
            totalMemoryField.SetValue(null, currentValue + long.MinValue);
    }
}

여기서는 호출하기 전에 앱에서 약 8엑사바이트를 할당해야 합니다.GC.Collect말할 필요도 없이, 만약 이것이 일어난다면 당신은 더 큰 문제를 해결해야 할 것입니다.:)

여러분이 언더플로의 된다면, 언플로가발가걱이경다사다용음니합을는우에더는정되능을 사용하세요.long.MinValue / 2.이 경우에도 여전히 4엑사바이트가 남아 있습니다.

:AddToTotal 실로 경검 수니다의 경계 합니다._totalMemory하지만 그것은 이것을 합니다.Debug.Assert 여기:

Debug.Assert(newValue >= 0);

Framework의 하게 되면 됩니다(.NET Framework와 함께 할 수 .ConditionalAttribute하지 않아도 됩니다.), 그서그것대걱필정요없가습다니할해래에▁),다없니습필.


이 접근 방식에서 어떤 문제가 발생할 수 있는지 물었습니다.어디 한번 봅시다.

  • 가장 확실한 것은 MS가 해킹하려는 WPF 코드를 변경한다는 것입니다.

    그렇다면, 변화의 성격에 따라 크게 달라질 수 있습니다.

    • 유형 이름/필드 이름/필드 유형을 변경합니다. 이 경우 해킹이 수행되지 않고 재고 동작으로 돌아갑니다.반사 코드는 상당히 방어적입니다. 예외를 두지 않을 것이고, 아무 것도 하지 않을 것입니다.

    • 그들은 그것을 바꿉니다.Debug.Assert릴리스 버전에서 활성화된 런타임 검사로 호출합니다.이 경우, 당신의 앱은 망하게 됩니다.디스크에서 이미지를 로드하려고 하면 느려집니다.

      이러한 위험은 자체 코드가 거의 해킹이라는 사실에 의해 완화됩니다.그들은 그것을 던질 의도가 없습니다. 그것은 눈에 띄지 않아야 합니다.그들은 조용히 앉아서 소리 없이 실패하기를 원합니다.이미지를 로드하는 것은 메모리 사용을 최소화하는 것이 유일한 목적인 일부 메모리 관리 코드에 의해 손상되어서는 안 되는 훨씬 더 중요한 기능입니다.

    • OP에 있는 원래 패치의 경우 상수 값이 변경되면 해킹이 작동을 멈출 수 있습니다.

    • 클래스와 필드를 그대로 유지하면서 알고리즘을 변경합니다.음...변화에 따라 어떤 일이든 일어날 수 있습니다.

  • 할 수 없게 한다고 가정해 보겠습니다.GC.Collect성공적으로 호출합니다.

    이 경우 명백한 위험은 메모리 사용량 증가입니다.수집 빈도가 줄어들기 때문에 주어진 시간에 더 많은 메모리가 할당됩니다.gen 0이 채워지면 수집이 자연스럽게 발생하기 때문에 큰 문제가 되지 않습니다.

    또한 메모리 조각화가 더 많아질 수 있으며, 이는 수집 횟수가 더 적어진 결과입니다.이것은 당신에게 문제가 될 수도 있고 아닐 수도 있습니다. 그러니 당신의 앱을 프로파일링하세요.

    컬렉션 수가 적으면 상위 세대로 승격되는 개체 수도 줄어듭니다.이것은 좋은 일입니다.이상적으로는 0세대에는 수명이 짧은 개체가 있고 2세대에는 수명이 긴 개체가 있어야 합니다.수집을 자주 하면 실제로 수명이 짧은 개체가 gen 1로 승격된 다음 gen 2로 승격되고 gen 2에서 도달할 수 없는 개체가 많이 생성됩니다.이것들은 gen 2 컬렉션에서만 정리되고 힙 조각화를 유발하며 힙을 압축하는 데 더 많은 시간을 소비해야 하기 때문에 실제로 GC 시간이 증가합니다.이것이 사실 전화를 하는 주된 이유입니다.GC.Collect자신은 나쁜 관행으로 간주됩니다. GC 전략을 적극적으로 거부하고 있으며, 이는 전체 애플리케이션에 영향을 미칩니다.

어떤 경우에도 올바른 접근 방식은 이미지를 로드하고 축소하여 UI에 미리 보기를 표시하는 것입니다.이 모든 처리는 백그라운드 스레드에서 수행해야 합니다.JPEG 이미지의 경우 내장된 미리 보기를 로드합니다. 충분히 적합할 수 있습니다.합니다. 은 "" "" "" "" "" "" "" "" "" ""를 무시합니다. 이는 완전히 우회합니다.MemoryPressure계급문제 네,도 바로 그렇게 그고네리, 그것바다제답것안입다;)

저는 당신이 가지고 있는 것이 괜찮다고 생각합니다.잘했어, 멋진 해킹, 리플렉션은 엉터리 프레임워크 코드를 수정하는 훌륭한 도구야.저는 그것을 여러 번 사용했습니다.목록 보기를 표시하는 보기로 사용을 제한하기만 하면 됩니다. 목록 보기를 항상 활성화하는 것은 너무 위험합니다.

근본적인 문제에 대해 조금 언급하자면, 끔찍한 프로세스 추가() 해킹은 물론 매우 조잡합니다.이는 비트맵 소스가 ID를 실행할 수 없기 때문입니다.문제가 있는 설계 결정으로 인해 SO는 이에 대한 질문으로 가득 차 있습니다.하지만, 그들 모두는 반대의 문제에 관한 것인데, 이 타이머는 따라잡기에 충분히 빠르지 않습니다.그것은 단지 잘 작동하지 않습니다.

이 코드의 작동 방식을 변경할 수 있는 방법은 없습니다.그것이 작용하는 값은 상수 선언입니다.15년 전에 적절했을 수도 있는 값을 기준으로 이 코드의 가능한 나이.1메가바이트에서 시작하여 "10MB"를 문제라고 부릅니다. 그 당시에는 생활이 더 간단했습니다. :) 그들은 제대로 확장되도록 쓰는 것을 잊었습니다. GC.Add Memory Pressure()는 아마 오늘 괜찮을 것입니다.너무 늦었지만, 그들은 프로그램 동작을 극적으로 바꾸지 않고는 더 이상 이 문제를 해결할 수 없습니다.

당신은 확실히 타이머를 물리치고 해킹을 피할 수 있습니다.확실히 지금 당신이 가지고 있는 문제는 그것의 간격이 사용자가 아무것도 읽지 않고 단지 관심 있는 기록을 찾으려고 할 때 목록 보기를 스크롤하는 속도와 거의 같다는 것입니다.수천 개의 행이 있는 목록 보기에서 매우 일반적인 UI 설계 문제로, 이 문제를 해결하고 싶지 않을 수도 있습니다.미리 보기를 캐시하여 다음에 필요한 미리 보기를 수집해야 합니다.이 작업을 수행하는 가장 좋은 방법은 스레드 풀 스레드에서 수행하는 것입니다.이 작업을 수행하는 동안 시간을 측정하면 최대 850msec을 사용할 수 있습니다.하지만 그 코드는 지금보다 작지 않을 것이고, 더 예쁘지도 않을 것입니다.

.NET 4.6.2는 MemoryPressure 클래스를 모두 제거하여 이 문제를 해결합니다.방금 프리뷰를 확인했는데 UI가 완전히 사라졌습니다.

.NET 4.6은 이를 구현합니다.

internal SafeMILHandleMemoryPressure(long gcPressure)
{
    this._gcPressure = gcPressure;
    this._refCount = 0;
    GC.AddMemoryPressure(this._gcPressure);
}

반면에 .NET 4.6.2 이전에는 GC를 강제하는 조잡한 메모리 압력 클래스가 있었습니다.할당한 WPF 비트맵의 양에 관계없이 850ms마다(WPF 비트맵이 할당되지 않은 경우) 또는 30초마다 수집합니다.

참고로 기존 핸들은 다음과 같이 구현되었습니다.

internal SafeMILHandleMemoryPressure(long gcPressure)
{
    this._gcPressure = gcPressure;
    this._refCount = 0;
    if (this._gcPressure > 8192L)
    {
        MemoryPressure.Add(this._gcPressure);   // Kills UI interactivity !!!!!
        return;
    }
    GC.AddMemoryPressure(this._gcPressure);
}

이는 문제를 다시 제기하기 위해 작성한 간단한 테스트 애플리케이션에서 GC 정지 시간이 극적으로 감소하는 것을 볼 수 있기 때문에 큰 차이가 있습니다.여기에 이미지 설명 입력

여기 보시는 것처럼 GC 서스펜션 시간은 2,71초에서 0,86초로 감소했습니다.이는 여러 GB의 관리 힙에서도 거의 일정하게 유지됩니다.이는 또한 백그라운드 GC가 다음과 같은 작업을 수행할 수 있기 때문에 전반적인 애플리케이션 성능을 향상시킵니다.뒤에.이렇게 하면 GC가 정리 작업을 수행하는 동안에도 원활하게 작업을 계속할 수 있는 관리되는 모든 스레드가 갑자기 중단되는 것을 방지할 수 있습니다.GC가 어떤 배경을 제공하는지 아는 사람은 많지 않지만, 일반적인 애플리케이션 워크로드의 경우 10-15% 정도 차이가 납니다.전체 GC에 몇 초가 걸릴 수 있는 다중 GB 관리 응용 프로그램을 사용하는 경우 극적인 개선을 볼 수 있습니다.일부 테스트에서는 애플리케이션에서 메모리 누수가 발생했습니다(5GB 관리 힙, 전체 GC 일시 중단 시간 7초). 이러한 강제 GC로 인해 35s UI 지연이 발생했습니다!

성찰 접근법을 사용하여 어떤 구체적인 문제에 직면할 수 있는지에 대한 업데이트된 질문에 대해, @HansPassant는 당신의 구체적인 접근법에 대한 그의 평가에 철저했다고 생각합니다.그러나 일반적으로 현재의 접근 방식으로 실행하는 위험은 소유하지 않은 코드에 대한 반사를 사용하여 실행하는 것과 동일한 위험이며, 다음 업데이트에서 이 위험은 사용자의 아래쪽에서 변경될 수 있습니다.당신이 그것에 만족하는 한, 당신이 가지고 있는 코드는 무시해도 될 위험이 있습니다.

원래의 질문에 답하기 위해, 아마도 해결할 방법이 있을 것입니다.GC.Collect(2)▁▁the▁byizing▁minim▁problem다니해결합의BitmapSourceㅠㅠ 샘플 입니다.아래는 제 생각을 보여주는 샘플 앱입니다.설명한 , 된 설한것유가게환상사용는되경을 합니다.ItemsControl디스크에서 미리 보기를 표시합니다.

다른 것들도 있을 수 있지만 주요 관심사는 축소판 그림 이미지가 어떻게 구성되는지입니다.애플리케이션이 다음 캐시를 생성합니다.WriteableBitmap목록 하므로 디스크에서. 목록 항목은 UI하여 이미지를 읽습니다.BitmapFrame이미지 정보(주로 픽셀 데이터)를 검색합니다.WriteableBitmap객체를 캐시에서 꺼내어 픽셀 데이터를 덮어쓴 다음 뷰 모델에 할당합니다.기존 목록 항목이 시야에서 벗어나 재활용됨에 따라,WriteableBitmap나중에 다시 사용할 수 있도록 개체가 캐시로 반환됩니다.한 일한유.BitmapSource전체 프로세스 동안 발생하는 관련 작업은 디스크에서 실제 이미지를 로드하는 작업입니다.

주목할 필요가 있는 것은, 이미지가 반환되었다는 것입니다.GetBitmapImageBytes()방법은 정확히 같은 크기여야 합니다.WriteableBitmap현재 256 x 256입니다.단순화를 위해 테스트에 사용한 비트맵 이미지는 이미 이 크기였지만 필요에 따라 스케일링을 구현하는 것은 사소한 일이어야 합니다.

주 창.xaml:

<Window x:Class="VirtualizedListView.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="500" Width="500">
    <Grid>
        <ItemsControl VirtualizingStackPanel.IsVirtualizing="True"
                      VirtualizingStackPanel.VirtualizationMode="Recycling"
                      VirtualizingStackPanel.CleanUpVirtualizedItem="VirtualizingStackPanel_CleanUpVirtualizedItem"
                      ScrollViewer.CanContentScroll="True"
                      ItemsSource="{Binding Path=Thumbnails}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Border BorderBrush="White" BorderThickness="1">
                        <Image Source="{Binding Image, Mode=OneTime}" Height="128" Width="128" />
                    </Border>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.Template>
                <ControlTemplate>
                    <Border BorderThickness="{TemplateBinding Border.BorderThickness}"
                            Padding="{TemplateBinding Control.Padding}"
                            BorderBrush="{TemplateBinding Border.BorderBrush}"
                            Background="{TemplateBinding Panel.Background}"
                            SnapsToDevicePixels="True">
                        <ScrollViewer Padding="{TemplateBinding Control.Padding}" Focusable="False">
                            <ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
                        </ScrollViewer>
                    </Border>
                </ControlTemplate>
            </ItemsControl.Template>
        </ItemsControl>
    </Grid>
</Window>

MainWindow.xaml.cs :

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;

namespace VirtualizedListView
{
    public partial class MainWindow : Window
    {
        private const string ThumbnailDirectory = @"D:\temp\thumbnails";

        private ConcurrentQueue<WriteableBitmap> _writeableBitmapCache = new ConcurrentQueue<WriteableBitmap>();

        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;

            // Load thumbnail file names
            List<string> fileList = new List<string>(System.IO.Directory.GetFiles(ThumbnailDirectory));

            // Load view-model
            Thumbnails = new ObservableCollection<Thumbnail>();
            foreach (string file in fileList)
                Thumbnails.Add(new Thumbnail(GetImageForThumbnail) { FilePath = file });

            // Create cache of pre-built WriteableBitmap objects; note that this assumes that all thumbnails
            // will be the exact same size.  This will need to be tuned for your needs
            for (int i = 0; i <= 99; ++i)
                _writeableBitmapCache.Enqueue(new WriteableBitmap(256, 256, 96d, 96d, PixelFormats.Bgr32, null));
        }

        public ObservableCollection<Thumbnail> Thumbnails
        {
            get { return (ObservableCollection<Thumbnail>)GetValue(ThumbnailsProperty); }
            set { SetValue(ThumbnailsProperty, value); }
        }
        public static readonly DependencyProperty ThumbnailsProperty =
            DependencyProperty.Register("Thumbnails", typeof(ObservableCollection<Thumbnail>), typeof(MainWindow));

        private BitmapSource GetImageForThumbnail(Thumbnail thumbnail)
        {
            // Get the thumbnail data via the proxy in the other app domain
            ImageLoaderProxyPixelData pixelData = GetBitmapImageBytes(thumbnail.FilePath);
            WriteableBitmap writeableBitmap;

            // Get a pre-built WriteableBitmap out of the cache then overwrite its pixels with the current thumbnail information.
            // This avoids the memory pressure being set in this app domain, keeping that in the app domain of the proxy.
            while (!_writeableBitmapCache.TryDequeue(out writeableBitmap)) { Thread.Sleep(1); }
            writeableBitmap.WritePixels(pixelData.Rect, pixelData.Pixels, pixelData.Stride, 0);

            return writeableBitmap;
        }

        private ImageLoaderProxyPixelData GetBitmapImageBytes(string fileName)
        {
            // All of the BitmapSource creation occurs in this method, keeping the calls to 
            // MemoryPressure.ProcessAdd() localized to this app domain

            // Load the image from file
            BitmapFrame bmpFrame = BitmapFrame.Create(new Uri(fileName));
            int stride = bmpFrame.PixelWidth * bmpFrame.Format.BitsPerPixel;
            byte[] pixels = new byte[bmpFrame.PixelHeight * stride];

            // Construct and return the image information
            bmpFrame.CopyPixels(pixels, stride, 0);
            return new ImageLoaderProxyPixelData()
            {
                Pixels = pixels,
                Stride = stride,
                Rect = new Int32Rect(0, 0, bmpFrame.PixelWidth, bmpFrame.PixelHeight)
            };
        }

        public void VirtualizingStackPanel_CleanUpVirtualizedItem(object sender, CleanUpVirtualizedItemEventArgs e)
        {
            // Get a reference to the WriteableBitmap before nullifying the property to release the reference
            Thumbnail thumbnail = (Thumbnail)e.Value;
            WriteableBitmap thumbnailImage = (WriteableBitmap)thumbnail.Image;
            thumbnail.Image = null;

            // Asynchronously add the WriteableBitmap back to the cache
            Dispatcher.BeginInvoke((Action)(() =>
            {
                _writeableBitmapCache.Enqueue(thumbnailImage);
            }), System.Windows.Threading.DispatcherPriority.Loaded);
        }
    }

    // View-Model
    public class Thumbnail : DependencyObject
    {
        private Func<Thumbnail, BitmapSource> _imageGetter;
        private BitmapSource _image;

        public Thumbnail(Func<Thumbnail, BitmapSource> imageGetter)
        {
            _imageGetter = imageGetter;
        }

        public string FilePath
        {
            get { return (string)GetValue(FilePathProperty); }
            set { SetValue(FilePathProperty, value); }
        }
        public static readonly DependencyProperty FilePathProperty =
            DependencyProperty.Register("FilePath", typeof(string), typeof(Thumbnail));

        public BitmapSource Image
        {
            get
            {
                if (_image== null)
                    _image = _imageGetter(this);
                return _image;
            }
            set { _image = value; }
        }
    }

    public class ImageLoaderProxyPixelData
    {
        public byte[] Pixels { get; set; }
        public Int32Rect Rect { get; set; }
        public int Stride { get; set; }
    }
}

벤치마크로서 (다른 사람이 없다면 저도 그렇게 생각합니다) Centrino 프로세서가 장착된 10년 된 노트북에서 이 접근 방식을 테스트해 보았지만 UI의 유동성에는 거의 문제가 없었습니다.

이에 대한 공로를 인정할 수 있기를 바라지만, 더 나은 답이 이미 있다고 생각합니다.xaml 창에서 ShowDialog를 호출할 때 가비지 수집이 호출되지 않도록 하려면 어떻게 해야 합니까?

ProcessAdd 메서드의 코드에서도 _totalMemory가 충분히 작으면 아무것도 실행되지 않는다는 것을 알 수 있습니다.그래서 저는 이 코드가 훨씬 사용하기 쉽고 부작용이 적다고 생각합니다.

typeof(BitmapImage).Assembly
  .GetType("MS.Internal.MemoryPressure")
  .GetField("_totalMemory", BindingFlags.NonPublic | BindingFlags.Static)
  .SetValue(null, Int64.MinValue / 2); 

그러나 우리는 이 방법이 무엇을 해야 하는지 이해해야 하며 .NET 소스의 의견은 매우 분명합니다.

/// Avalon currently only tracks unmanaged memory pressure related to Images.  
/// The implementation of this class exploits this by using a timer-based
/// tracking scheme. It assumes that the unmanaged memory it is tracking
/// is allocated in batches, held onto for a long time, and released all at once
/// We have profiled a variety of scenarios and found images do work this way

그래서 제 결론은 그들의 코드를 비활성화함으로써 이미지가 관리되는 방식 때문에 메모리가 가득 찰 위험이 있다는 것입니다.하지만 당신이 사용하는 애플리케이션이 크고 GC가 필요할 수 있다는 것을 알고 있기 때문입니다.콜렉트 콜렉션이라고 불리는 매우 간단하고 안전한 해결책은 당신이 할 수 있다고 느낄 때 스스로 콜렉션을 부르는 것입니다.

이 코드는 사용된 총 메모리가 임계값을 초과할 때마다 타이머를 사용하여 이를 실행하려고 하므로 너무 자주 발생하지 않습니다.그것은 그들에게 30초일 것입니다.그럼 GC에 전화해 보는 게 어때요?양식을 닫거나 많은 이미지를 사용할 수 있는 다른 작업을 수행할 때 수집(2)?또는 컴퓨터가 유휴 상태이거나 앱의 초점이 맞지 않을 때 등?

저는 _totalMemory 값이 어디에서 왔는지 확인하는 데 시간을 들였고, 그들이 WritableBitmap을 만들 때마다 _totalMemory에 대한 메모리를 추가하는 것 같습니다. 여기서 http://referencesource.microsoft.com/PresentationCore/R/dca5f18570fed771.html 을 다음과 같이 계산합니다.pixelWidth * pixelHeight * pixelFormat.InternalBitsPerPixel / 8 * 2;Frezables와 함께 작동하는 방법에 대해서도 설명합니다.거의 모든 WPF 컨트롤의 그래픽 표현에 의해 할당된 메모리를 추적하기 위한 내부 메커니즘입니다.

_totalMemory를 매우 낮은 값으로 설정할 수 있었을 뿐만 아니라 메커니즘을 하이잭할 수 있었던 것처럼 들립니다.가끔 값을 읽고, 처음에 뺀 큰 값을 추가하고, 그린 컨트롤에서 사용하는 메모리의 실제 값을 가져와 GC 여부를 결정할 수 있습니다.수집하든 말든.

언급URL : https://stackoverflow.com/questions/36044796/any-way-to-workaround-wpfs-calling-of-gc-collect2-aside-from-reflection

반응형