वर्कर थ्रेड के माध्यम से मैं ऑब्जर्वेबल कॉलेक्शन कैसे अपडेट कर सकता हूं?


83

मुझे एक ObservableCollection<A> a_collection;संग्रह मिला है जिसमें 'n' आइटम हैं। प्रत्येक आइटम इस तरह दिखता है:

public class A : INotifyPropertyChanged
{

    public ObservableCollection<B> b_subcollection;
    Thread m_worker;
}

मूल रूप से, यह सब WPF लिस्टव्यू + डिटेल्स व्यू कंट्रोल, जो b_subcollectionचयनित आइटम को एक अलग लिस्टव्यू में दिखाता है (2-वे बाइंडिंग, प्रॉपर्टीचार्ज आदि पर अपडेट)।

मेरे लिए समस्या तब दिखाई दी जब मैंने थ्रेडिंग को लागू करना शुरू किया। संपूर्ण विचार यह था कि इसका पूरा a_collectionउपयोग श्रमिक सूत्र "काम करने" के लिए करें और फिर अपने संबंधित को अपडेट करें b_subcollectionsऔर वास्तविक समय में परिणाम दिखाए।

जब मैंने इसकी कोशिश की, तो मुझे यह कहते हुए एक अपवाद मिला कि केवल डिस्पैचर थ्रेड एक ऑब्जर्वेबल कॉलेक्शन को संशोधित कर सकता है, और काम रुक गया।

किसी को भी समस्या की व्याख्या कर सकते हैं, और इसे कैसे प्राप्त करें?


निम्नलिखित लिंक का प्रयास करें जो एक थ्रेड-सुरक्षित समाधान प्रदान करता है जो किसी भी थ्रेड से काम करता है और कई UI थ्रेड्स के माध्यम से बाध्य हो सकता है: codeproject.com/Articles/64936/…
एंथनी

जवाबों:


74

तकनीकी रूप से समस्या यह नहीं है कि आप बैकग्राउंड थ्रेड से ऑब्जर्वेबल कॉलेक्शन को अपडेट कर रहे हैं। समस्या यह है कि जब आप ऐसा करते हैं, तो संग्रह उसी थ्रेड पर अपने कलेक्शन की घटना को बढ़ाता है, जो बदलाव का कारण बनता है - जिसका अर्थ है कि नियंत्रण एक पृष्ठभूमि थ्रेड से अपडेट किया जा रहा है।

एक पृष्ठभूमि थ्रेड से एक संग्रह को पॉप्युलेट करने के लिए, जबकि नियंत्रण इसके लिए बाध्य होते हैं, आपको इसे संबोधित करने के लिए स्क्रैच से अपना स्वयं का संग्रह प्रकार बनाना होगा। एक सरल विकल्प है जो आपके लिए काम कर सकता है।

UI थ्रेड पर कॉल जोड़ें।

public static void AddOnUI<T>(this ICollection<T> collection, T item) {
    Action<T> addMethod = collection.Add;
    Application.Current.Dispatcher.BeginInvoke( addMethod, item );
}

...

b_subcollection.AddOnUI(new B());

यह विधि तुरंत वापस आ जाएगी (इससे पहले कि आइटम वास्तव में संग्रह में जोड़ा जाता है) तब UI थ्रेड पर, आइटम को संग्रह में जोड़ा जाएगा और सभी को खुश होना चाहिए।

हालांकि, वास्तविकता यह है कि यह समाधान सभी क्रॉस-थ्रेड गतिविधि के कारण भारी भार के नीचे होने की संभावना है। एक अधिक कुशल समाधान वस्तुओं के एक समूह को बैच देगा और उन्हें समय-समय पर UI थ्रेड में पोस्ट करेगा, ताकि आप प्रत्येक आइटम के लिए थ्रेड्स पर कॉल न कर सकें।

BackgroundWorker वर्ग के औजार एक पैटर्न है कि आप अपने के माध्यम से प्रगति की रिपोर्ट करने की अनुमति देता ReportProgress एक पृष्ठभूमि आपरेशन के दौरान विधि। प्रगति के माध्यम से UI थ्रेड पर प्रगति की सूचना दी जाती है। यह आपके लिए एक और विकल्प हो सकता है।


BackgroundWorker के रनवॉकर के बारे में क्या है? कि यूआई धागे के रूप में अच्छी तरह से बाध्य है?
मैकीक

1
हाँ, जिस तरह से BackgroundWorker को डिज़ाइन किया गया है, उसके पूरा होने और प्रगति की घटनाओं को बढ़ाने के लिए SynchronizationContext.Current का उपयोग करना है। DoWork इवेंट बैकग्राउंड थ्रेड पर चलेगा। यहाँ कि BackgroundWorker भी चर्चा करता है WPF में सूत्रण के बारे में एक अच्छा लेख है msdn.microsoft.com/en-us/magazine/cc163328.aspx#S4
जोश

5
यह उत्तर अपनी सादगी में सुंदर है। इसे साझा करने के लिए धन्यवाद!
बीकर जूल

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

@ जोश मैंने अपना उत्तर हटा दिया है, क्योंकि मेरा मामला विशेष प्रतीत होता है। मैं अपने डिजाइन में आगे देखूंगा और फिर से सोचूंगा कि क्या बेहतर किया जा सकता है।
माइकल

125

.NET 4.5 के लिए नया विकल्प

.NET 4.5 से शुरू करके संग्रह तक पहुंच को स्वचालित रूप से सिंक्रनाइज़ करने और CollectionChangedइवेंट को UI थ्रेड में भेजने के लिए एक अंतर्निहित तंत्र है । इस सुविधा को सक्षम करने के लिए आपको अपने UI थ्रेड के भीतर से कॉल करना होगा ।BindingOperations.EnableCollectionSynchronization

EnableCollectionSynchronization दो काम करता है:

  1. उस थ्रेड को याद रखता है, जहाँ से उसे कॉल किया जाता है और CollectionChangedउस थ्रेड पर मार्शल्स ईवेंट के लिए डेटा बाइंडिंग पाइपलाइन का कारण बनता है ।
  2. संग्रह पर एक ताला लगाता है जब तक कि मार्शडेल्ड इवेंट को संभाला नहीं गया है, ताकि UI थ्रेड चलाने वाले इवेंट हैंडलर्स संग्रह को पढ़ने का प्रयास नहीं करेंगे, जबकि यह एक पृष्ठभूमि थ्रेड से संशोधित किया जा रहा है।

बहुत महत्वपूर्ण बात, यह सब कुछ का ध्यान नहीं रखता है : थ्रेड-सुरक्षित संग्रह को थ्रेड-सुरक्षित संग्रह तक पहुंच सुनिश्चित करने के लिए आपको अपने बैकग्राउंड थ्रेड्स से उसी लॉक को प्राप्त करके ढांचे के साथ सहयोग करना होगा जब संग्रह संशोधित होने वाला हो।

इसलिए सही संचालन के लिए आवश्यक कदम हैं:

1. तय करें कि आप किस तरह के लॉकिंग का उपयोग कर रहे हैं

यह निर्धारित करेगा कि किस अधिभार का EnableCollectionSynchronizationउपयोग किया जाना चाहिए। ज्यादातर समय एक साधारण lockबयान पर्याप्त होगा, इसलिए यह अधिभार मानक विकल्प है, लेकिन अगर आप कुछ फैंसी सिंक्रोनाइज़ेशन तंत्र का उपयोग कर रहे हैं तो कस्टम लॉक के लिए भी समर्थन है

2. संग्रह बनाएं और सिंक्रनाइज़ेशन सक्षम करें

चुने हुए लॉक तंत्र के आधार पर, UI थ्रेड पर उपयुक्त अधिभार को कॉल करें । यदि एक मानक lockकथन का उपयोग करते हुए आपको तर्क के रूप में लॉक ऑब्जेक्ट प्रदान करने की आवश्यकता होती है। यदि कस्टम सिंक्रनाइज़ेशन का उपयोग करते हुए आपको एक CollectionSynchronizationCallbackप्रतिनिधि और एक संदर्भ वस्तु (जो हो सकती है null) प्रदान करने की आवश्यकता है । जब आह्वान किया जाता है, तो इस प्रतिनिधि को आपके कस्टम लॉक का अधिग्रहण करना चाहिए, Actionइसे पारित करने के लिए आमंत्रित करें और लौटने से पहले लॉक जारी करें।

3. इसे संशोधित करने से पहले संग्रह को लॉक करके सहयोग करें

जब आप इसे स्वयं संशोधित करने जा रहे हों, तो आपको उसी तंत्र का उपयोग करके संग्रह को लॉक करना होगा; के साथ ऐसा lock()ही ताला वस्तु को पारित कर दिया पर EnableCollectionSynchronizationसरल परिदृश्य में, या कस्टम परिदृश्य में एक ही कस्टम समन्वयन तंत्र के साथ।


2
क्या यह संग्रह अपडेट को तब तक अवरुद्ध करने का कारण बनता है जब तक UI थ्रेड उन्हें संभालने के लिए चारों ओर नहीं पहुंच जाता? अपरिवर्तनीय वस्तुओं (एक अपेक्षाकृत सामान्य परिदृश्य) के वन-वे डेटा बाउंड संग्रह वाले परिदृश्यों में, ऐसा प्रतीत होता है कि एक संग्रह वर्ग रखना संभव होगा जो प्रत्येक ऑब्जेक्ट के "अंतिम प्रदर्शित संस्करण" के साथ-साथ एक परिवर्तन कतार भी रखेगा। , और BeginInvokeएक विधि को चलाने के लिए उपयोग करें जो यूआई थ्रेड में सभी उपयुक्त परिवर्तन BeginInvokeकरेगा [अधिकांश में किसी भी समय लंबित होगा।
सुपरकाट

16
एक छोटा सा उदाहरण इस उत्तर को और अधिक उपयोगी बना देगा। मुझे लगता है कि यह शायद सही समाधान है, लेकिन मुझे नहीं पता कि इसे कैसे लागू किया जाए।
रबरडक

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

2
मैं अब तक एक महीने के लिए 4.5 में संग्रह तुल्यकालन देख रहा हूं, और मुझे नहीं लगता कि इस उत्तर में से कुछ सही है। उत्तर बताता है कि सक्षम कॉल UI थ्रेड पर होनी चाहिए और कॉलबैक UI थ्रेड पर होती है। इनमें से कोई भी मामला नहीं लगता है। मैं एक पृष्ठभूमि थ्रेड पर संग्रह सिंक्रनाइज़ेशन को सक्षम करने में सक्षम हूं और अभी भी इस तंत्र का उपयोग करता हूं। इसके अलावा, ढांचे में गहरी कॉल किसी भी मार्शलिंग (। सीएफ ViewManager.AccessCollection नहीं करते referencesource.microsoft.com/#PresentationFramework/src/... )
रेजिनाल्ड ब्लू

2
EnableCollectionSynchronization के बारे में इस सूत्र के उत्तर से अधिक जानकारी है: stackoverflow.com/a/16511740/2887274
मैथ्यू एस

22

.NET 4.0 के साथ आप इन वन-लाइनर्स का उपयोग कर सकते हैं:

.Add

Application.Current.Dispatcher.BeginInvoke(new Action(() => this.MyObservableCollection.Add(myItem)));

.Remove

Application.Current.Dispatcher.BeginInvoke(new Func<bool>(() => this.MyObservableCollection.Remove(myItem)));

11

पश्चात के लिए संग्रह सिंक्रनाइज़ेशन कोड। यह संग्रह सिंक को सक्षम करने के लिए सरल लॉक तंत्र का उपयोग करता है। ध्यान दें कि आपको UI थ्रेड पर संग्रह सिंक को सक्षम करना होगा।

public class MainVm
{
    private ObservableCollection<MiniVm> _collectionOfObjects;
    private readonly object _collectionOfObjectsSync = new object();

    public MainVm()
    {

        _collectionOfObjects = new ObservableCollection<MiniVm>();
        // Collection Sync should be enabled from the UI thread. Rest of the collection access can be done on any thread
        Application.Current.Dispatcher.BeginInvoke(new Action(() => 
        { BindingOperations.EnableCollectionSynchronization(_collectionOfObjects, _collectionOfObjectsSync); }));
    }

    /// <summary>
    /// A different thread can access the collection through this method
    /// </summary>
    /// <param name="newMiniVm">The new mini vm to add to observable collection</param>
    private void AddMiniVm(MiniVm newMiniVm)
    {
        lock (_collectionOfObjectsSync)
        {
            _collectionOfObjects.Insert(0, newMiniVm);
        }
    }
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.