2009-11-18 10 views
31

मैं आम तौर पर एक फार्म पर इस तरह कोड है:सी #: क्या मुझे रनटाइम पर बनाए गए पृष्ठभूमिवर्कर को निपटाने की ज़रूरत है?

private void PerformLongRunningOperation() 
    { 
     BackgroundWorker worker = new BackgroundWorker(); 

     worker.DoWork += delegate 
     { 
      // perform long running operation here 
     }; 

     worker.RunWorkerAsync(); 
    } 

इसका मतलब यह है कि मैं BackgroundWorker निपटान नहीं है, जबकि मुझे लगता है कि यह निपटाए मिलेगा अगर मैं तो प्रपत्र डिजाइनर द्वारा यह जोड़ा था।

क्या इससे कोई समस्या पैदा होगी? क्या मॉड्यूल-स्तर _saveWorker घोषित करना अधिक सही है, और उसके बाद फॉर्म की निपटान विधि से Dispose पर कॉल करें?

उत्तर

32

हां, आपको पृष्ठभूमि कार्यकर्ता का निपटान करना चाहिए।

आपको ThreadPool.QueueUserWorkItem(...) का उपयोग करना आसान हो सकता है जिसके बाद किसी भी सफाई की आवश्यकता नहीं होती है।


क्यों तुम हमेशा निपटान() बुलाना चाहिए पर अतिरिक्त विवरण:

हालांकि अगर आप BackgroundWorker वर्ग में देखने के लिए यह वास्तव में किसी भी धागा साफ यह निपटाने विधि है में ऐसा नहीं करता है, यह अभी भी महत्वपूर्ण है कॉल करने के लिए कक्षा को कचरा कलेक्टर पर प्रभाव के कारण निपटाना है।

अंतिमकर्ताओं के साथ कक्षाएं तुरंत जीसीड नहीं हैं। उन्हें रखा जाता है और फाइनलजर कतार में जोड़ा जाता है। फाइनलज़र थ्रेड तब चलता है, (जो मानक पैटर्न कॉल का निपटान करता है)। इसका मतलब है कि ऑब्जेक्ट जीसी पीढ़ी 1 में जीवित रहेगा। और जीन 1 संग्रह जीन 0 संग्रह से कहीं दुर्लभ हैं, इसलिए आप स्मृति में चारों ओर घूमते हैं।

यदि आप निपटान() को कॉल करते हैं, तो ऑब्जेक्ट को अंतिमकरण कतार में जोड़ा नहीं जाएगा, इसलिए कचरा इकट्ठा करने के लिए स्वतंत्र है।

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

तो मुझे लगता है कि, बिलकुल भी, यह 100% कठिन और तेज़ आवश्यकता नहीं है। यदि आप निपटान() को कॉल नहीं करते हैं, तो आपका ऐप विस्फोट (या यहां तक ​​कि स्मृति को भी रिसाव नहीं करेगा), लेकिन कुछ परिस्थितियों में इसका नकारात्मक प्रभाव हो सकता है। पृष्ठभूमि कार्यकर्ता को WinForms घटक के रूप में उपयोग के लिए डिज़ाइन किया गया था, इसलिए इस तरह इसका उपयोग करें, यदि आपके पास अलग-अलग आवश्यकताएं हैं और WinForms घटक के रूप में इसका उपयोग नहीं करना चाहते हैं, तो इसका उपयोग न करें, नौकरी के लिए सही टूल का उपयोग करें, थ्रेडपूल की तरह।

+8

_Why_ क्या उसे बीजीडब्ल्यू कॉल करना चाहिए। डिसस्पेक्ट()? सामान्य के अलावा 'क्योंकि यह वहां है' तर्क। –

+0

@ हेंक। मैंने कुछ अतिरिक्त विवरण जोड़ा है। यह 100% महत्वपूर्ण नहीं है, लेकिन इसे कॉल करने के लिए डिज़ाइन किया गया था। यदि आप इसे कॉल नहीं करना चाहते हैं तो अन्य कक्षाएं हैं जो गैर-विनफॉर्म कोड के लिए बेहतर फिट हैं। –

6

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

पृष्ठभूमिवर्कर को घटक से IDisposable() विरासत में मिला है लेकिन वास्तव में इसकी आवश्यकता नहीं है।

अपनी विधि को सीधे थ्रेडपूल पर धक्का देने के लिए इसकी तुलना करें। आप धागे का निपटान नहीं कर सकते (निश्चित रूप से नहीं) पूल से नहीं।

लेकिन यदि आपका नमूना इस अर्थ में पूरा हो गया है कि आप पूर्ण घटना या प्रगति/रद्द सुविधाओं का उपयोग नहीं कर रहे हैं तो आप ThreadPool.QueueUserWorkItem() का भी उपयोग कर सकते हैं।

+2

थ्रेड ऑब्जेक्ट्स बहुत महंगे हैं (वे, कम से कम, बड़ी आरक्षित स्टैक मेमोरी, शायद 1 एमबी) होते हैं। – denisenkom

+0

हां, वे महंगे हैं। लेकिन आप उन्हें कैसे साफ करेंगे? इसके अलावा, बीजीडब्ल्यू थ्रेडपूल का उपयोग करता है। –

+1

बीजीडब्लू पर निपटान के लिए कम से कम 2 अच्छे कारण हैं। सबसे पहले यह जीसी.SuppressFinalize() को कॉल करता है, और दूसरी बात यह है कि वेन भविष्य के कार्यान्वयन के बारे में बताता है और आंतरिक कार्यान्वयन विवरण पर निर्भर नहीं है। –

16

चुनौती यह सुनिश्चित कर रही है कि BackgroundWorker के बाद यह केवल समाप्त हो गया है। आप Completed ईवेंट में ऐसा नहीं कर सकते हैं, क्योंकि वह ईवेंट पृष्ठभूमिवर्कर द्वारा स्वयं उठाया जाता है।

पृष्ठभूमिवर्कर वास्तव में WinForms फॉर्म पर एक घटक के रूप में उपयोग करने का इरादा रखता है, इसलिए मैं ऐसा करने की सलाह दूंगा, या Thread.QueueUserWorkItem जैसे कुछ पर स्विच करूँगा। यह थ्रेड-पूल थ्रेड का उपयोग करेगा, और इसे समाप्त होने पर किसी भी विशेष सफाई की आवश्यकता नहीं होगी।

+4

+1 'पृष्ठभूमिवर्कर' के लिए WinForms घटक के रूप में उपयोग करने का इरादा है। – Brian

2

यह सभी IDisposable ऑब्जेक्ट्स के लिए निपटान() को कॉल करने का सबसे अच्छा अभ्यास माना जाता है। इससे उन्हें हैंडल जैसे अप्रबंधित संसाधनों को जारी करने की अनुमति मिलती है। IDISposable कक्षाओं में भी अंतिमकर्ता होना चाहिए, जिनकी मौजूदगी उस समय में देरी कर सकती है जिस पर जीसी को उन वस्तुओं को पूरी तरह से इकट्ठा करने की अनुमति है।

यदि आपकी कक्षा एक आईडीस्पोजेबल आवंटित करती है और इसे सदस्य चर के लिए असाइन करती है, तो सामान्य रूप से इसे स्वयं भी IDISposable होना चाहिए।

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

+1

"आईडीस्पोजेबल क्लास में फाइनलाइज़र भी होना चाहिए, जिनकी उपस्थिति उस समय में देरी कर सकती है जिस पर जीसी को उन वस्तुओं को पूरी तरह इकट्ठा करने की अनुमति है।" <- यह केवल तभी सही है जब IDISposable में – dss539

+0

को साफ करने के लिए * अन * प्रबंधित संसाधन हैं, मेरा सुझाव है कि यदि ऑब्जेक्ट को अप्रबंधित संसाधनों से निपटना पड़ता है तो आमतौर पर एक ऑब्जेक्ट को केवल आईडीस्पोज़बल लागू करना चाहिए; अन्यथा जीसी सभी काम करता है (शायद उन मामलों को छोड़कर जहां आप "उपयोग" कथन के अर्थशास्त्र का लाभ उठाने में सक्षम होना चाहते हैं)। साथ ही, IDISposable ऑब्जेक्ट्स अक्सर अन्य IDiposables का संदर्भ देते हैं, इसलिए बहुत से लोग अप्रबंधित संसाधनों को सीधे नहीं रखते हैं, बल्कि अप्रत्यक्ष रूप से। – RickNZ

7

मेरे विचार में, सामान्य रूप से, यदि यह IDISposable है, तो यह आपके द्वारा किए जाने पर निपटान() डी होना चाहिए। यहां तक ​​कि यदि पृष्ठभूमिवर्कर के वर्तमान कार्यान्वयन को तकनीकी रूप से निपटाने की आवश्यकता नहीं है, तो आप बाद में आंतरिक कार्यान्वयन से आश्चर्यचकित नहीं होना चाहते हैं।

+1

मुझे लगता है कि भविष्य के कार्यान्वयन के बारे में यह एक बहुत अच्छा मुद्दा है। हां, वर्तमान बीजीडब्ल्यू इस निपटान विधि में कुछ भी नहीं करता है, लेकिन .NET 4.0 बीजीडब्ल्यू की गारंटी देने का कोई तरीका नहीं है, वहां कुछ साफ नहीं होगा। –

+1

यह अच्छा लगता है लेकिन सच्चाई यह है कि बीजीडब्ल्यू। इसका उद्देश्य सिर्फ बेस क्लास से विरासत है। –

+1

@henk यह ** ** के लिए ** है। एमएस एक दिन की तुलना में कोई गारंटी नहीं देता है, यह ** नहीं होगा, जो ** ** आईडीआईस्पोज़बल 'लागू करने वाली चीजों पर हमेशा' निपटान() 'को कॉल करने की सलाह देते हैं। –

3

क्यों एक उपयोग कथन में लपेटें नहीं? ज्यादा नहीं अतिरिक्त प्रयास और आप निपटान मिलती है:

private void PerformLongRunningOperation() 
    { 
     using (BackgroundWorker worker = new BackgroundWorker()) 
     { 
      worker.DoWork += delegate 
          { 
           // perform long running operation here 
          }; 
      worker.RunWorkerAsync(); 
     } 
    } 

संपादित करें:

ठीक है, मैं एक साथ रखा एक छोटे से परीक्षण थोड़े देखने के लिए निपटान और whatnot के साथ चल रहा है:

using System; 
using System.ComponentModel; 
using System.Threading; 

namespace BackgroundWorkerTest 
{ 
    internal class Program 
    { 
     private static BackgroundWorker _privateWorker; 

     private static void Main() 
     { 
      PrintThread("Main"); 
      _privateWorker = new BackgroundWorker(); 
      _privateWorker.DoWork += WorkerDoWork; 
      _privateWorker.RunWorkerCompleted += WorkerRunWorkerCompleted; 
      _privateWorker.Disposed += WorkerDisposed; 
      _privateWorker.RunWorkerAsync(); 
      _privateWorker.Dispose(); 
      _privateWorker = null; 

      using (var BW = new BackgroundWorker()) 
      { 
       BW.DoWork += delegate 
           { 
            Thread.Sleep(2000); 
            PrintThread("Using Worker Working"); 
           }; 
       BW.Disposed += delegate { PrintThread("Using Worker Disposed"); }; 
       BW.RunWorkerCompleted += delegate { PrintThread("Using Worker Completed"); }; 
       BW.RunWorkerAsync(); 
      } 

      Console.ReadLine(); 
     } 

     private static void WorkerDisposed(object sender, EventArgs e) 
     { 
      PrintThread("Private Worker Disposed"); 
     } 

     private static void WorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
     { 
      PrintThread("Private Worker Completed"); 
     } 

     private static void WorkerDoWork(object sender, DoWorkEventArgs e) 
     { 
      Thread.Sleep(2000); 
      PrintThread("Private Worker Working"); 
     } 

     private static void PrintThread(string caller) 
     { 
      Console.WriteLine("{0} Thread: {1}", caller, Thread.CurrentThread.ManagedThreadId); 
     } 
    } 
} 

,

Main Thread: 1 
Private Worker Disposed Thread: 1 
Using Worker Disposed Thread: 1 
Private Worker Working Thread: 3 
Using Worker Working Thread: 4 
Using Worker Completed Thread: 4 
Private Worker Completed Thread: 3 

कुछ परीक्षण से यह प्रतीत होता है निपटान() पर मूल रूप से कोई प्रभाव नहीं है कि: यहाँ उत्पादन होता है एक आरंभिक BackgroundWorker। चाहे आप इसे किसी उपयोग कथन के दायरे में बुलाएं या नहीं, या इसे कोड में घोषित करें और तुरंत इसका निपटान करें और इसे अस्वीकार करें, यह अभी भी सामान्य रूप से चलता है। विस्थापित घटना मुख्य धागे पर होती है, और DoWork और RunWorker थ्रेड पूल थ्रेड्स पर पूर्ण होता है (जो भी घटना आग लगने पर उपलब्ध होता है)। मैंने एक ऐसे मामले की कोशिश की जहां मैंने निपटान कहा जाने के ठीक बाद RunWorker पूर्ण घटना को अनियंत्रित किया (इसलिए DoWork को पूरा करने का मौका मिलने से पहले) और RunWorker पूर्ण नहीं हुआ। इससे मुझे विश्वास होता है कि आप अभी भी पृष्ठभूमि कार्यकर्ता ऑब्जेक्ट का निपटारा कर सकते हैं।

इसलिए जैसा कि अन्य ने उल्लेख किया है, इस समय, ऐसा लगता है कि निपटान निपटाना वास्तव में आवश्यक नहीं है। हालांकि, मुझे कम से कम मेरे अनुभव और इन परीक्षणों से ऐसा करने में कोई नुकसान नहीं दिख रहा है।

+3

क्या यह काम करता है? क्या यह कार्यकर्ता को बहुत जल्दी नहीं लेगा? – RickL

+0

मैंने इसे कई बार उपयोग किया है और यह निश्चित रूप से "काम करता है" - यह पृष्ठभूमि प्रक्रिया को बहुत जल्दी नहीं मारता है। हालांकि, अगर मुझे वास्तव में सही काम करना है या यह वास्तव में निपटान करता है तो मुझे कभी भी एक निश्चित उत्तर नहीं मिला है। मैं किसी ऐसे व्यक्ति की उम्मीद कर रहा था जो निश्चित रूप से जानता है कि उस पर वजन होगा। –

+0

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

3

कॉल अपने RunWorkerCompleted ईवेंट में निपटान करें।

BackgroundWorker wkr = new BackgroundWorker(); 
wkr.DoWork += (s, e) => { 
    // Do long running task. 
}; 
wkr.RunWorkerCompleted += (s, e) => { 
    try { 
     if (e.Error != null) { 
      // Handle failure. 
     } 
    } finally { 
     // Use wkr outer instead of casting. 
     wkr.Dispose(); 
    } 
}; 
wkr.RunWorkerAsync(); 

अतिरिक्त ट्राई/अंत में सुनिश्चित करने के लिए कि क्या आपका पूरा होने के कोड एक अपवाद को जन्म देती है Dispose कहा जाता हो जाता है।

0

पूरा होल्डर मूल धागे पर चलाया जाता है (यानी, थ्रेड पूल से पृष्ठभूमि धागा नहीं)! आपके परीक्षा परिणाम वास्तव में उस आधार की पुष्टि करते हैं।

संबंधित मुद्दे