2012-05-03 19 views
14

यहां एक दिलचस्प लाइब्रेरी लेखक की दुविधा है। मेरी लाइब्रेरी में (मेरे मामले में EasyNetQ) मैं थ्रेड स्थानीय संसाधनों को आवंटित कर रहा हूं। तो जब कोई ग्राहक एक नया धागा बनाता है और फिर मेरी लाइब्रेरी पर कुछ तरीकों को कॉल करता है तो नए संसाधन बनाए जाते हैं। EasyNetQ के मामले में जब RabbitMQ सर्वर के लिए एक नया चैनल बनाया जाता है तो क्लाइंट नए थ्रेड पर 'प्रकाशित' कहता है। मैं यह पता लगाने में सक्षम होना चाहता हूं कि क्लाइंट थ्रेड निकलता है ताकि मैं संसाधन (चैनल) को साफ़ कर सकूं।जब कोई क्लाइंट थ्रेड निकलता है तो मैं कैसे पता लगा सकता हूं?

ऐसा करने का एकमात्र तरीका मैं एक नया 'वॉचर' थ्रेड बनाने के लिए आया हूं जो क्लाइंट थ्रेड में एक जॉइन कॉल पर बस ब्लॉक करता है। यहां एक साधारण प्रदर्शन:

पहले मेरी 'लाइब्रेरी'। यह ग्राहक धागा पकड़ लेता है और फिर एक नया धागा 'शामिल' पर जो ब्लॉक बनाता है:

public class Library 
{ 
    public void StartSomething() 
    { 
     Console.WriteLine("Library says: StartSomething called"); 

     var clientThread = Thread.CurrentThread; 
     var exitMonitorThread = new Thread(() => 
     { 
      clientThread.Join(); 
      Console.WriteLine("Libaray says: Client thread existed"); 
     }); 

     exitMonitorThread.Start(); 
    } 
} 

यहाँ एक ग्राहक मेरे पुस्तकालय का उपयोग करता है है। यह एक नया धागा बनाता है और फिर मेरी लाइब्रेरी की StartSomething विधि कॉल:

public class Client 
{ 
    private readonly Library library; 

    public Client(Library library) 
    { 
     this.library = library; 
    } 

    public void DoWorkInAThread() 
    { 
     var thread = new Thread(() => 
     { 
      library.StartSomething(); 
      Thread.Sleep(10); 
      Console.WriteLine("Client thread says: I'm done"); 
     }); 
     thread.Start(); 
    } 
} 

जब मैं इस तरह ग्राहक चलाएँ:

Library says: StartSomething called 
Client thread says: I'm done 
Libaray says: Client thread existed 

तो यह काम करता है:

var client = new Client(new Library()); 

client.DoWorkInAThread(); 

// give the client thread time to complete 
Thread.Sleep(100); 

मैं इस आउटपुट प्राप्त , लेकिन यह बदसूरत है। मुझे वास्तव में इन सभी अवरुद्ध वॉचर धागे के बारे में विचार पसंद नहीं है। क्या ऐसा करने का कोई बेहतर तरीका है?

पहला विकल्प।

एक ऐसी विधि प्रदान करें जो एक कार्यकर्ता लौटाती है जो आईडीस्पोज़ेबल लागू करती है और दस्तावेज़ीकरण में स्पष्ट करती है कि आपको थ्रेड्स के बीच श्रमिकों को साझा नहीं करना चाहिए।

public class Library 
{ 
    public LibraryWorker GetLibraryWorker() 
    { 
     return new LibraryWorker(); 
    } 
} 

public class LibraryWorker : IDisposable 
{ 
    public void StartSomething() 
    { 
     Console.WriteLine("Library says: StartSomething called"); 
    } 

    public void Dispose() 
    { 
     Console.WriteLine("Library says: I can clean up"); 
    } 
} 

ग्राहक अब एक छोटे से अधिक जटिल है:: यहाँ संशोधित पुस्तकालय है

public class Client 
{ 
    private readonly Library library; 

    public Client(Library library) 
    { 
     this.library = library; 
    } 

    public void DoWorkInAThread() 
    { 
     var thread = new Thread(() => 
     { 
      using(var worker = library.GetLibraryWorker()) 
      { 
       worker.StartSomething(); 
       Console.WriteLine("Client thread says: I'm done"); 
      } 
     }); 
     thread.Start(); 
    } 
} 

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

गैर-ब्रेकिंग दूसरा विकल्प। एपीआई क्लाइंट के लिए 'कार्यक्षेत्र' घोषित करने का एक तरीका प्रदान करता है। एक बार दायरा पूरा हो जाने पर, लाइब्रेरी साफ हो सकती है। पुस्तकालय एक WorkScope कि IDisposable लागू करता है प्रदान करता है, लेकिन इसके बाद के संस्करण पहले विकल्प के विपरीत, StartSomething विधि पुस्तकालय वर्ग पर रहता है:

public class Library 
{ 
    public WorkScope GetWorkScope() 
    { 
     return new WorkScope(); 
    } 

    public void StartSomething() 
    { 
     Console.WriteLine("Library says: StartSomething called"); 
    } 
} 

public class WorkScope : IDisposable 
{ 
    public void Dispose() 
    { 
     Console.WriteLine("Library says: I can clean up"); 
    } 
} 

ग्राहक बस एक WorkScope में StartSomething कॉल डालता है ...

public class Client 
{ 
    private readonly Library library; 

    public Client(Library library) 
    { 
     this.library = library; 
    } 

    public void DoWorkInAThread() 
    { 
     var thread = new Thread(() => 
     { 
      using(library.GetWorkScope()) 
      { 
       library.StartSomething(); 
       Console.WriteLine("Client thread says: I'm done"); 
      } 
     }); 
     thread.Start(); 
    } 
} 

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

+2

'मैं थ्रेड स्थानीय संसाधनों को आवंटित कर रहा हूं' - यह अच्छी शुरुआत नहीं है :( –

+2

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

+0

यह सुनिश्चित नहीं है कि मैं उपयोग केस और नमूना कोड को सही ढंग से सहसंबंधित करता हूं। क्या EasyNetQ के क्लाइंट कोड को एक नया प्रारंभ करने की आवश्यकता होगी लाइब्रेरी का उपयोग करने के लिए थ्रेड? क्या आप अधिक वास्तविक क्लाइंट कोड डाल सकते हैं? यदि नहीं, तो var ctx = library.StartExecutionContext(); ... ctx.Complete(); फिर लाइब्रेरी ctx.Complete के अंदर एक मैनुअल रीसेट इवेंट का उपयोग करके, सफाई होगी –

उत्तर

1

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

इस तरह के सभी lib कॉल का 99% काम करता है जहां राज्य क्लाइंट कॉल में संरक्षित किया जाना चाहिए, उदाहरण के लिए। सॉकेट हैंडल, फ़ाइल हैंडल।

0

आपका .Join समाधान मेरे लिए बहुत सुंदर लग रहा है। अवरुद्ध वॉचर धागे इतनी भयानक चीज नहीं हैं।

+1

ओह हाँ वे हैं! –

+0

ठीक है, सभी चीजें संयम में।: पी वास्तव में कोई अन्य तरीका नहीं है जिसे मैं यह सुनिश्चित करने के बारे में जानता हूं कि धागा पूरा हो गया है। – IngisKahn

+0

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

3

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

public interface IThreadCompletedNotifier 
{ 
    event Action ThreadCompleted; 
} 

public class Library 
{ 
    public void StartSomething(IThreadCompletedNotifier notifier) 
    { 
     Console.WriteLine("Library says: StartSomething called"); 
     notifier.ThreadCompleted +=() => Console.WriteLine("Libaray says: Client thread existed"); 
     var clientThread = Thread.CurrentThread; 
     exitMonitorThread.Start(); 
    } 
} 

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

public class Client : IThreadCompletedNotifier 
{ 
    private readonly Library library; 

    public event Action ThreadCompleted; 

    public Client(Library library) 
    { 
     this.library = library; 
    } 

    public void DoWorkInAThread() 
    { 
     var thread = new Thread(() => 
     { 
      library.StartSomething(); 
      Thread.Sleep(10); 
      Console.WriteLine("Client thread says: I'm done"); 
      if(ThreadCompleted != null) 
      { 
       ThreadCompleted(); 
      } 
     }); 
     thread.Start(); 
    } 
} 
+0

यह बेहतर है - ग्राहक को अधिसूचना प्रदान करनी चाहिए। अधिसूचना में एक पैरामीटर होना चाहिए जो संसाधन को जारी किया जा रहा है। यह पैरामीटर आवंटित संसाधन के लिए एक हैंडल/टोकन है, यानी। थ्रेड आईडी या ऐसी किसी भी चीज़ के साथ कुछ भी नहीं करना है, अन्यथा लाइब्रेरी पूल किए गए धागे, 'हरे' धागे/फाइबर या थ्रेड जो कभी समाप्त नहीं होती है, लूप के आसपास बेकार हो जाती है और अलग-अलग संसाधनों का उपयोग अलग-अलग या अलग-अलग समय में करना चाहती है। –

+0

यह मेरा पहला विचार था, एक ऑपरेशन कॉन्टेक्स्ट प्रदान करें जो आईडीस्पोज़ेबल लागू किया गया है, फिर सभी थ्रेड स्थानीय कॉल को उपयोग कथन में निष्पादित किया जा सकता है। मुझे लगता है कि मैं खोजना चाहता था 'यह सिर्फ काम करता है' विकल्प पहले, लेकिन मैं एक विशिष्ट एपीआई के विचार के आसपास आ रहा हूं। –

+3

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

1

, मैं एक ही धागे में सब देख रहा है गठबंधन करने की कोशिश करेगा कि चुनाव सभी धागे है कि .ThreadState संपत्ति अपनी लाइब्रेरी को हिट करें, कहें, हर 100 एमएमएस (मुझे यकीन नहीं है कि आपको संसाधनों को साफ करने की कितनी जल्दी जरूरत है ...)

+1

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

+0

@MartinJames पूल धागे की निगरानी क्यों नहीं की जा सकती? पुस्तकालय का उपयोग करने वाले हर धागे को मतदान के लिए पंजीकृत किया जा सकता है। मुझे लगता है कि यह समाधान समझ में आता है। – usr

+0

@ मार्टिन मैं समझता हूं कि आप क्या कह रहे हैं, और मुझे लगता है कि यह बिना कहने के चला जाता है कि थ्रेड-क्लोजर का पता लगाने की सफाई करने का एकमात्र तरीका (और यहां तक ​​कि पसंदीदा विधि भी नहीं) है। यह उपयोगकर्ता त्रुटि के खिलाफ लाइब्रेरी के लिए सिर्फ एक अतिरिक्त सुरक्षा है। आप सही हैं: यदि उपयोगकर्ता पूल किए गए धागे या जीयूआई थ्रेड पर अपने हैंडल बंद नहीं करते हैं, तो वे हमेशा के लिए लटकेंगे। –

5

आप एक थ्रेड स्थैतिक मॉनीटर बना सकते हैं जिसमें फाइनलाइज़र है। जब धागा जीवित होता है, तो यह मॉनीटर ऑब्जेक्ट को पकड़ लेगा। जब थाड मर जाता है तो यह इसे रोकना बंद कर देगा। बाद में, जब जीसी किक करता है, तो यह आपके मॉनीटर को अंतिम रूप देगा। फाइनल में आप एक ऐसी घटना को बढ़ा सकते हैं जो क्लाइंट थ्रेड की (मनाई गई) मौत के बारे में आपके ढांचे को सूचित करेगी।

एक नमूना कोड इस सार में पाया जा सकता है: https://gist.github.com/2587063

यहाँ यह की एक प्रति है:

public class ThreadMonitor 
{ 
    public static event Action<int> Finalized = delegate { }; 
    private readonly int m_threadId = Thread.CurrentThread.ManagedThreadId; 

    ~ThreadMonitor() 
    { 
     Finalized(ThreadId); 
    } 

    public int ThreadId 
    { 
     get { return m_threadId; } 
    } 
} 

public static class Test 
{ 
    private readonly static ThreadLocal<ThreadMonitor> s_threadMonitor = 
     new ThreadLocal<ThreadMonitor>(() => new ThreadMonitor()); 

    public static void Main() 
    { 
     ThreadMonitor.Finalized += i => Console.WriteLine("thread {0} closed", i); 
     var thread = new Thread(() => 
     { 
      var threadMonitor = s_threadMonitor.Value; 
      Console.WriteLine("start work on thread {0}", threadMonitor.ThreadId); 
      Thread.Sleep(1000); 
      Console.WriteLine("end work on thread {0}", threadMonitor.ThreadId); 
     }); 
     thread.Start(); 
     thread.Join(); 

     // wait for GC to collect and finalize everything 
     GC.GetTotalMemory(forceFullCollection: true); 

     Console.ReadLine(); 
    } 
} 

मुझे आशा है कि यह मदद करता है। मुझे लगता है कि यह आपके अतिरिक्त प्रतीक्षा धागे से अधिक सुरुचिपूर्ण है।

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

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