c# - हरण - प्रतिबिंब से अलग जीसी.कोलेक्ट(2) के डब्ल्यूपीएफ के कॉलिंग को काम करने का कोई तरीका?




समतल दर्पण द्वारा बना प्रतिबिंब कैसा होता है (4)

मुझे हाल ही में एक WPF कक्षा में निजी क्षेत्रों में हेरफेर करने के लिए उत्पादन कोड में इस monstrosity में जांचना पड़ा: (tl; डॉ मैं इसे करने से कैसे बचूं?)

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();
        }
    }
}

यह समझने के लिए कि मैं कुछ पागल क्यों करूँगा, आपको MS.Internal.MemoryPressure.ProcessAdd() को देखने की आवश्यकता है:

/// <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);
}

हां, यह वास्तव में एक जेन 2 कचरा संग्रह को मजबूर करने वाला डब्ल्यूपीएफ है, जो एक पूर्ण अवरोधक जीसी को मजबूर करता है। एक स्वाभाविक रूप से होने वाला जीसी जीन 2 ढेर पर अवरुद्ध किए बिना होता है। अभ्यास में इसका क्या अर्थ है कि जब भी इस विधि को बुलाया जाता है, तो हमारा पूरा ऐप लॉक हो जाता है। आपका ऐप जितना अधिक मेमोरी उपयोग कर रहा है, और आपके जेन 2 ढेर जितना अधिक खंडित होगा, उतना ही लंबा होगा। हमारा ऐप वर्तमान में काफी मात्रा में डेटा कैश करता है और आसानी से स्मृति की एक गड़बड़ी ले सकता है और मजबूर जीसी कई मिनटों के लिए धीमे डिवाइस पर हमारे ऐप को लॉक कर सकता है - हर 850 एमएस।

इसके विपरीत लेखक के विरोध के बावजूद, एक परिदृश्य में पहुंचना आसान है जहां इस विधि को महान आवृत्ति के साथ बुलाया जाता है। फ़ाइल से BitmapSource लोड करते समय WPF का यह मेमोरी कोड होता है। हम हजारों वस्तुओं के साथ एक सूचीदृश्य वर्चुअलाइज करते हैं जहां डिस्क पर संग्रहीत थंबनेल द्वारा प्रत्येक आइटम का प्रतिनिधित्व किया जाता है। जैसे ही हम नीचे स्क्रॉल करते हैं, हम उन थंबनेल में गतिशील रूप से लोड हो रहे हैं, और जीसी अधिकतम आवृत्ति पर हो रहा है। इसलिए स्क्रॉलिंग ऐप लॉकिंग के साथ अविश्वसनीय रूप से धीमी और चंचल हो जाती है।

उस भयानक प्रतिबिंब हैक के साथ मैंने ऊपर उल्लेख किया है, हम टाइमर को कभी पूरा नहीं करने के लिए मजबूर करते हैं, और इस प्रकार डब्ल्यूपीएफ कभी भी जीसी को मजबूर नहीं करता है। इसके अलावा, कोई प्रतिकूल परिणाम नहीं दिखता है - स्मृति एक स्क्रॉल के रूप में बढ़ती है और आखिरकार जीसी मुख्य धागे को लॉक किए बिना स्वाभाविक रूप से ट्रिगर किया जाता है।

क्या उन कॉल को GC.Collect(2) रोकने के लिए कोई अन्य विकल्प है जो मेरे समाधान के रूप में इतना स्पष्ट रूप से घृणित नहीं है? इस हैक के साथ निम्नलिखित समस्याओं से उत्पन्न होने वाली ठोस समस्याओं के बारे में स्पष्टीकरण प्राप्त करना अच्छा लगेगा। इसके द्वारा मेरा मतलब है GC.Collect(2) को कॉल से बचने में समस्याएं। चयन GC.Collect(2) । (मुझे लगता है कि जीसी प्राकृतिक रूप से पर्याप्त होना चाहिए)


नोटिस: यह केवल तभी करें जब यह आपके ऐप में बाधा उत्पन्न कर लेता है, और सुनिश्चित करें कि आप परिणामों को समझते हैं - हंस के जवाब को अच्छी व्याख्या के लिए देखें कि उन्होंने इसे पहले स्थान पर क्यों रखा है।

फ्रेमवर्क में एक बुरा हैक को ठीक करने की कोशिश करने के लिए आपके पास कुछ बुरा कोड है ... क्योंकि यह सभी स्थैतिक है और डब्ल्यूपीएफ में कई स्थानों से बुलाया जाता है, आप इसे तोड़ने के लिए उपयोग प्रतिबिंब से बेहतर नहीं कर सकते हैं (अन्य समाधान बहुत खराब होंगे )।

तो वहां एक साफ समाधान की उम्मीद मत करो। ऐसी कोई चीज मौजूद नहीं है जब तक वे डब्ल्यूपीएफ कोड नहीं बदलते।

लेकिन मुझे लगता है कि आपका हैक सरल हो सकता है और टाइमर का उपयोग करने से बच सकता है: बस _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 एक्साबाइट आवंटित करना होगा। चयन करें। कहने की जरूरत नहीं है, अगर ऐसा होता है तो आपको हल करने में बड़ी समस्याएं होंगी। :)

यदि आप अंडरफ्लो की संभावना के बारे में चिंतित हैं, तो बस long.MinValue / 2 उपयोग करें। long.MinValue / 2 रूप में long.MinValue / 2 । यह अभी भी आपको 4 एक्साबाइट्स के साथ छोड़ देता है।

ध्यान दें कि AddToTotal वास्तव में _totalMemory की सीमाओं की जांच करता है, लेकिन यह एक _totalMemory साथ करता है here सम्मिलित here :

Debug.Assert(newValue >= 0);

चूंकि आप .NET Framework के रिलीज़ संस्करण का उपयोग करेंगे, इन आवेदकों को अक्षम कर दिया जाएगा (एक ConditionalAttribute एट्रिब्यूट के साथ), इसलिए इसके बारे में चिंता करने की आवश्यकता नहीं है।

आपने पूछा है कि इस दृष्टिकोण के साथ क्या समस्याएं उत्पन्न हो सकती हैं। चलो एक नज़र डालते हैं।

  • सबसे स्पष्ट एक: एमएस डब्ल्यूपीएफ कोड बदलता है जिसे आप हैक करने की कोशिश कर रहे हैं।

    खैर, उस मामले में, यह बदलाव की प्रकृति पर काफी निर्भर करता है।

    • वे प्रकार का नाम / फ़ील्ड नाम / फ़ील्ड प्रकार बदलते हैं: उस स्थिति में, हैक नहीं किया जाएगा, और आप स्टॉक व्यवहार पर वापस आ जाएंगे। प्रतिबिंब कोड बहुत रक्षात्मक है, यह अपवाद नहीं फेंक देगा, यह कुछ भी नहीं करेगा।

    • वे Debug.Assert को एक रनटाइम चेक में कॉल करते हैं जो रिलीज़ संस्करण में सक्षम है। उस स्थिति में, आपका ऐप बर्बाद हो गया है। डिस्क से छवि लोड करने का कोई भी प्रयास फेंक देगा। उफ़।

      यह जोखिम इस तथ्य से कम हो गया है कि उनका अपना कोड काफी हैक है। वे इसे फेंकने का इरादा नहीं रखते हैं, इसे ध्यान में जाना चाहिए। वे चाहते हैं कि यह चुप बैठे और चुपचाप विफल हो जाएं। छवियों को लोड करना एक और अधिक महत्वपूर्ण विशेषता है जिसे कुछ मेमोरी प्रबंधन कोड से प्रभावित नहीं किया जाना चाहिए जिसका एकमात्र उद्देश्य स्मृति उपयोग को कम से कम रखना है।

    • ओपी में आपके मूल पैच के मामले में, यदि वे निरंतर मान बदलते हैं, तो आपका हैक काम करना बंद कर सकता है।

    • कक्षा और क्षेत्र को बरकरार रखते हुए वे एल्गोरिदम बदलते हैं। खैर ... परिवर्तन के आधार पर कुछ भी हो सकता है।

  • अब, मान लें कि हैक काम करता है और जीसी को अक्षम करता है। कॉल कॉल सफलतापूर्वक।

    इस मामले में स्पष्ट जोखिम स्मृति उपयोग में वृद्धि हुई है। चूंकि संग्रह कम बार-बार होंगे, इसलिए किसी भी समय अधिक मेमोरी आवंटित की जाएगी। यह एक बड़ा मुद्दा नहीं होना चाहिए, क्योंकि जीन 0 भरने पर संग्रह अभी भी स्वाभाविक रूप से तब भी होते हैं।

    आपके पास अधिक स्मृति विखंडन भी होगा, यह कम संग्रह का प्रत्यक्ष परिणाम है। यह आपके लिए कोई समस्या हो सकती है या नहीं भी हो सकती है - इसलिए अपना ऐप प्रोफाइल करें।

    कम संग्रह का मतलब है कि कम वस्तुओं को उच्च पीढ़ी को बढ़ावा दिया जाता है। यह एक अच्छी बात है। आदर्श रूप से, आपके पास जीन 0 में अल्पकालिक वस्तुएं होनी चाहिए, और जीन 2 में लंबे समय तक रहने वाली वस्तुएं होनी चाहिए। अक्सर संग्रह सामान्य रूप से अल्पकालिक वस्तुओं को जीन 1 और फिर जीन 2 में बढ़ावा देने के लिए प्रेरित करेंगे, और आप समाप्त हो जाएंगे जीन 2 में कई पहुंच योग्य वस्तुओं। इन्हें केवल एक जीन 2 संग्रह के साथ साफ किया जाएगा, ढेर विखंडन का कारण बन जाएगा, और वास्तव में जीसी समय में वृद्धि होगी, क्योंकि इसे ढेर को कम करने में अधिक समय व्यतीत करना होगा। यह वास्तव में प्राथमिक कारण है कि जीसी को बुलावा क्यों करें GC.Collect स्वयं को GC.Collect एक बुरा अभ्यास माना जाता है - आप सक्रिय रूप से जीसी रणनीति को हरा रहे हैं, और यह पूरे आवेदन को प्रभावित करता है।

किसी भी मामले में, सही दृष्टिकोण छवियों को लोड करना, उन्हें स्केल करना और आपके यूआई में इन थंबनेल प्रदर्शित करना होगा। यह सब प्रसंस्करण पृष्ठभूमि धागे में किया जाना चाहिए। जेपीईजी छवियों के मामले में, एम्बेडेड थंबनेल लोड करें - वे काफी अच्छे हो सकते हैं। और ऑब्जेक्ट पूल का उपयोग करें ताकि आपको प्रत्येक बार नए बिटमैप्स को तुरंत चालू करने की आवश्यकता न हो, यह पूरी तरह से MemoryPressure क्लास समस्या को छोड़ देता है। और हाँ, यह वही है जो अन्य उत्तरों सुझाव देते हैं;)


.NET 4.6.2 मेमोरीप्रेस श्रेणी को पूरी तरह मारकर इसे ठीक करेगा। मैंने अभी पूर्वावलोकन की जांच की है और मेरे यूआई हैंग पूरी तरह से चले गए हैं।

.NET 4.6 इसे लागू करता है

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

जबकि पूर्व .NET 4.6.2 में आपके पास यह क्रूड मेमोरीप्रेसर क्लास था जो जीसी को मजबूर करेगा। प्रत्येक 850 एमएमएस (यदि कोई डब्ल्यूपीएफ बिटमैप्स आवंटित नहीं किया गया था) में चयन करें या प्रत्येक 30s चाहे आप कितने डब्ल्यूपीएफ बिटमैप्स आवंटित किए गए हों।

संदर्भ के लिए पुराने हैंडल को लागू किया गया था

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);
}

यह एक बड़ा अंतर बनाता है क्योंकि आप जीसी निलंबन के समय को एक साधारण परीक्षण एप्लिकेशन में नाटकीय रूप से ड्रॉप कर सकते हैं जिसे मैंने इस मुद्दे को दोबारा लिखने के लिए लिखा था।

यहां आप देखते हैं कि जीसी निलंबन का समय 2,71 से घटकर 0,86 हो गया है। बहु जीबी प्रबंधित ढेर के लिए यह लगभग स्थिर रहता है। यह समग्र अनुप्रयोग प्रदर्शन को भी बढ़ाता है क्योंकि अब पृष्ठभूमि जीसी अपना काम कर सकती है जहां इसे चाहिए: पृष्ठभूमि में। यह सभी प्रबंधित धागे के अचानक रुकावट को रोकता है जो खुशी से काम करना जारी रख सकते हैं हालांकि जीसी चीजों की सफाई कर रहा है। बहुत से लोग इस बात से अवगत नहीं हैं कि पृष्ठभूमि जीसी उन्हें क्या देती है लेकिन इससे सीए का असली दुनिया अंतर होता है। सामान्य अनुप्रयोग वर्कलोड के लिए 10-15%। यदि आपके पास एक बहु जीबी प्रबंधित एप्लिकेशन है जहां एक पूर्ण जीसी सेकंड ले सकता है तो आपको नाटकीय सुधार दिखाई देगा। कुछ परीक्षणों में एक एप्लीकेशन में मेमोरी रिसाव था (5 जीबी प्रबंधित ढेर, पूर्ण जीसी निलंबन समय 7 एस) मैंने इन जबरन जीसी के कारण 35s यूआई देरी देखी थी!


मुझे लगता है कि आपके पास क्या है ठीक है। अच्छा किया, अच्छा हैक, प्रतिबिंब भद्दा फ्रेमवर्क कोड को ठीक करने के लिए एक शानदार उपकरण है। मैंने इसे कई बार इस्तेमाल किया है। बस इसके उपयोग को उस दृश्य में सीमित करें जो ListView प्रदर्शित करता है, यह हर समय सक्रिय होने के लिए बहुत खतरनाक है।

अंतर्निहित समस्या के बारे में कुछ नूडलिंग, भयंकर प्रक्रिया ऐड () हैक निश्चित रूप से बहुत कच्ची है। यह बिटमैपसोर्स का एक परिणाम है जो IDISposable लागू नहीं कर रहा है। एक संदिग्ध डिजाइन निर्णय, SO इसके बारे में प्रश्नों से भरा है। हालांकि, उनमें से सभी विपरीत समस्या के बारे में हैं, यह टाइमर जारी रखने के लिए पर्याप्त तेज़ नहीं है। यह सिर्फ बहुत अच्छा काम नहीं करता है।

इस कोड के काम को बदलने के तरीके को बदलने के लिए आप कुछ भी नहीं कर सकते हैं। जो मूल्य यह काम करता है वह स्थिर घोषणाएं हैं। 15 साल पहले उचित मूल्यों के आधार पर, इस कोड की संभावित आयु। यह एक मेगाबाइट से शुरू होता है और "10 एमबी एमबी" को एक समस्या का आह्वान करता है, फिर जीवन वापस आसान था :) वे इसे लिखना भूल गए थे, इसलिए यह ठीक से स्केल करता है, जीसी। एडमेमरीप्रेस () शायद आज ठीक होगा। बहुत देर हो चुकी है, वे अब प्रोग्राम व्यवहार में नाटकीय रूप से बदलाव किए बिना इसे ठीक नहीं कर सकते हैं।

आप निश्चित रूप से टाइमर को हरा सकते हैं और अपने हैक से बच सकते हैं। निश्चित रूप से आपके पास अभी समस्या यह है कि इसका अंतराल उसी दर के समान होता है जिस पर उपयोगकर्ता सूची दृश्य के माध्यम से स्क्रॉल करता है जब वह कुछ भी नहीं पढ़ता है लेकिन केवल ब्याज के रिकॉर्ड को खोजने का प्रयास करता है। यह एक यूआई डिज़ाइन समस्या है जो हजारों पंक्तियों के साथ सूची दृश्यों के साथ बहुत आम है, एक समस्या जिसे आप शायद संबोधित नहीं करना चाहते हैं। आपको क्या करना है थंबनेल कैश करना, उन लोगों को इकट्ठा करना जिन्हें आप जानते हैं, आपको शायद अगले की आवश्यकता होगी। ऐसा करने का सबसे अच्छा तरीका थ्रेडपूल थ्रेड में ऐसा करना है। जब आप ऐसा करते हैं तो समय मापें, आप 850 एमएससी तक खर्च कर सकते हैं। हालांकि, यह कोड अब आपके पास जितना छोटा नहीं होगा, उतना ही ज्यादा सुंदर नहीं होगा।


रिफ्लेक्शन दृष्टिकोण का उपयोग कर ठोस समस्याएं क्या हैं, इसके बारे में अद्यतन प्रश्न के लिए, मुझे लगता है कि @ हंसपैसेंट आपके विशिष्ट दृष्टिकोण के मूल्यांकन में पूरी तरह से था। लेकिन अधिक आम तौर पर बोलते हुए, आपके वर्तमान दृष्टिकोण के साथ चलने वाला जोखिम वह जोखिम है जिसे आप अपने स्वामित्व वाले कोड के खिलाफ किसी भी प्रतिबिंब का उपयोग करके चलाते हैं; यह अगले अपडेट में आपके नीचे बदल सकता है। जब तक आप इसके साथ सहज महसूस करते हैं, तब आपके पास कोड में नगण्य जोखिम होना चाहिए।

उम्मीद है कि मूल प्रश्न का उत्तर देने के लिए, BitmapSource ऑपरेशंस की संख्या को कम करके BitmapSourceBitmapSource GC.Collect(2) समस्या को हल करने का एक तरीका हो सकता है। नीचे एक नमूना ऐप है जो मेरे विचार को दिखाता है। आपने जो वर्णन किया है उसके समान, यह डिस्क से थंबनेल प्रदर्शित करने के लिए वर्चुअलाइज्ड ItemsControl का उपयोग करता है।

जबकि अन्य हो सकते हैं, ब्याज का मुख्य बिंदु यह है कि थंबनेल छवियों का निर्माण कैसे किया जाता है। ऐप WriteableBitmap ऑब्जेक्ट्स का कैश बनाता है। चूंकि सूची आइटम यूआई द्वारा अनुरोध किए जाते हैं, यह छवि जानकारी पुनर्प्राप्त करने के लिए BitmapFrame का उपयोग करके, डिस्क से छवि को पढ़ता है, मुख्य रूप से पिक्सेल डेटा। एक WriteableBitmap ऑब्जेक्ट कैश से खींचा जाता है, यह पिक्सेल डेटा ओवरराइट किया जाता है, फिर इसे व्यू-मॉडल को असाइन किया जाता है। चूंकि मौजूदा सूची आइटम दृश्य से बाहर हो जाते हैं और पुनर्नवीनीकरण किए जाते हैं, WriteableBitmap ऑब्जेक्ट को बाद में पुन: उपयोग के लिए कैश में वापस कर दिया जाता है। उस पूरी प्रक्रिया के दौरान किए गए एकमात्र BitmapSource संबंधित गतिविधि डिस्क से छवि की वास्तविक लोडिंग है।

यह ध्यान देने योग्य है कि, GetBitmapImageBytes() विधि द्वारा लौटाई गई छवि को इस पिक्सेल-ओवरराइट दृष्टिकोण के लिए GetBitmapImageBytes() कैश के समान आकार होना चाहिए; वर्तमान में 256 x 256. सादगी के लिए, मेरे परीक्षण में उपयोग की जाने वाली बिटमैप छवियां पहले से ही इस आकार में थीं, लेकिन आवश्यकतानुसार स्केलिंग को लागू करने के लिए यह छोटा होना चाहिए।

MainWindow.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; }
    }
}

एक बेंचमार्क के रूप में, (मेरे लिए अगर कोई और नहीं, मुझे लगता है) मैंने इस दृष्टिकोण का परीक्षण सेंट्रिनो प्रोसेसर के साथ 10 वर्षीय लैपटॉप पर किया था और यूआई में तरलता के साथ लगभग कोई समस्या नहीं थी।





garbage-collection