2016-01-04 9 views
7

मेरे पास कोड ब्लॉक की एक श्रृंखला है जो बहुत अधिक समय ले रही है। जब यह विफल रहता है तो मुझे किसी भी प्रकार की आवश्यकता नहीं होती है। असल में, मैं एक अपवाद फेंकना चाहता हूं जब ये ब्लॉक बहुत अधिक समय लेते हैं, और हमारे मानक त्रुटि प्रबंधन से बाहर निकलते हैं। मैं प्रत्येक ब्लॉक से विधियों को नहीं बनाना पसंद करूंगा (जो अब तक केवल एक ही सुझाव हैं जिन्हें मैंने देखा है), क्योंकि इसे कोड बेस के एक बड़े पुनर्लेख की आवश्यकता होगी।विभिन्न कोड ब्लॉक के लिए जेनेरिक टाइमआउट ऑब्जेक्ट कैसे बनाएं?

यदि संभव हो, तो मैं जैसे को बनाने के लिए यहां हूं।

public void MyMethod(...) 
{ 

... 

    using (MyTimeoutObject mto = new MyTimeoutObject(new TimeSpan(0,0,30))) 
    { 
     // Everything in here must complete within the timespan 
     // or mto will throw an exception. When the using block 
     // disposes of mto, then the timer is disabled and 
     // disaster is averted. 
    } 

... 
} 

मैंने टाइमर क्लास का उपयोग करके ऐसा करने के लिए एक साधारण वस्तु बनाई है। (ध्यान दें कि उन/पेस्ट कॉपी किए जाने के लिए: इस कोड काम नहीं करता !!)

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Timers; 

    public class MyTimeoutObject : IDisposable 
    { 
     private Timer timer = null; 

     public MyTimeoutObject (TimeSpan ts) 
     { 
      timer = new Timer(); 
      timer.Elapsed += timer_Elapsed; 
      timer.Interval = ts.TotalMilliseconds; 

      timer.Start(); 
     } 

     void timer_Elapsed(object sender, ElapsedEventArgs e) 
     { 
      throw new TimeoutException("A code block has timed out."); 
     } 

     public void Dispose() 
     { 
      if (timer != null) 
      { 
       timer.Stop(); 
      } 
     } 
    } 

यह काम नहीं करता क्योंकि System.Timers.Timer वर्ग कैप्चर, अवशोषित करता है और भीतर फेंक दिया किसी भी अपवाद पर ध्यान नहीं देता, जो - जैसा कि मैंने पाया है - मेरे डिजाइन को हराता है। कुल वर्गीकरण के बिना इस वर्ग/कार्यक्षमता को बनाने का कोई अन्य तरीका?

यह दो घंटे पहले इतना आसान लग रहा था, लेकिन मुझे बहुत सिरदर्द पैदा कर रहा है।

+0

क्या आप ऊपरी स्तर पर किसी भी अपवाद को संभालने वाले हैं, जहां से आप इस विधि को कॉल करते हैं? इसके अलावा, क्या यह एक बहु थ्रेडेड वातावरण है? – Shaharyar

+0

आप 'रद्दीकरण टोकन' की एक स्थिर दुकान का उपयोग कर सकते हैं और उन सभी जगहों का उपयोग कर सकते हैं जिनके पास आपके पास कार्य है, या कम सुरुचिपूर्ण लेकिन शायद अधिक प्रभावी - एक और प्रक्रिया है जो आपकी मुख्य प्रक्रिया को मार सकती है और स्थानीय HTTP या नामित पाइपों पर लागू की जाती है –

+0

शाहरारी: हाँ। पकड़ श्रृंखला दूर है। और हाँ, यह एक बहु थ्रेडेड वातावरण है। लाइब्रेरी जो मैं लिख रहा हूं वह WinForms अनुप्रयोगों के भीतर प्रयोग की जानी चाहिए, न कि वेब पर। – Jerry

उत्तर

2

ठीक है, मैंने इस पर कुछ समय बिताया है और मुझे लगता है कि मेरे पास एक समाधान है जो आपके कोड को जितना भी बदल देगा, उतना ही आपके लिए काम करेगा।

निम्नलिखित है कि आप बनाए गए Timebox वर्ग का उपयोग कैसे करेंगे।

public void MyMethod(...) { 

    // some stuff 

    // instead of this 
    // using(...){ /* your code here */ } 

    // you can use this 
    var timebox = new Timebox(TimeSpan.FromSeconds(1)); 
    timebox.Execute(() => 
    { 
     /* your code here */ 
    }); 

    // some more stuff 

} 

यहां Timebox काम करता है।

  • एक Timebox वस्तु के साथ बनाई गई है एक दिया Timespan
  • जब Execute कहा जाता है, Timebox एक TimeboxRuntime वस्तु संदर्भ धारण करने के लिए एक बच्चे AppDomain बनाता है, और यह
  • TimeboxRuntime में वस्तु के लिए एक प्रॉक्सी रिटर्न बच्चे AppDomain बच्चे
  • Timebox के भीतर निष्पादित करने के लिए इनपुट के रूप में Action लेता है, तो TimeboxRuntime प्रॉक्सी को कॉल करने के लिए एक कार्य बनाता है y
  • कार्य शुरू कर दिया है (और कार्रवाई निष्पादन शुरू होता है), और "मुख्य" के लिए धागा प्रतीक्षा करता है जब तक दिए गए TimeSpan
  • के बाद दिया TimeSpan (या जब काम पूरा करता है), बच्चे AppDomain है के रूप में के लिए अनलोड किया गया कि Action पूरा हो गया था या नहीं। action का समय समाप्त, नहीं तो अगर action एक अपवाद फेंकता है, यह बच्चे AppDomain से पकड़ा और बुला AppDomain के लिए लौट आए

फेंकने के लिए एक नकारात्मक पक्ष यह है कि अपने कार्यक्रम ऊंचा आवश्यकता होगी है अगर

  • एक TimeoutException फेंक दिया जाता है AppDomain बनाने के लिए पर्याप्त अनुमतियां।

    यहां एक नमूना कार्यक्रम है जो दर्शाता है कि यह कैसे काम करता है (मुझे विश्वास है कि यदि आप सही using एस) शामिल करते हैं तो आप इसे कॉपी-पेस्ट कर सकते हैं। यदि आप रुचि रखते हैं तो मैंने this gist भी बनाया है।

    public class Program 
    { 
        public static void Main() 
        { 
         try 
         { 
          var timebox = new Timebox(TimeSpan.FromSeconds(1)); 
          timebox.Execute(() => 
          { 
           // do your thing 
           for (var i = 0; i < 1000; i++) 
           { 
            Console.WriteLine(i); 
           } 
          }); 
    
          Console.WriteLine("Didn't Time Out"); 
         } 
         catch (TimeoutException e) 
         { 
          Console.WriteLine("Timed Out"); 
          // handle it 
         } 
         catch(Exception e) 
         { 
          Console.WriteLine("Another exception was thrown in your timeboxed function"); 
          // handle it 
         } 
         Console.WriteLine("Program Finished"); 
         Console.ReadLine(); 
        } 
    } 
    
    public class Timebox 
    { 
        private readonly TimeSpan _ts; 
    
        public Timebox(TimeSpan ts) 
        { 
         _ts = ts; 
        } 
    
        public void Execute(Action func) 
        { 
         AppDomain childDomain = null; 
         try 
         { 
          // Construct and initialize settings for a second AppDomain. Perhaps some of 
          // this is unnecessary but perhaps not. 
          var domainSetup = new AppDomainSetup() 
          { 
           ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase, 
           ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile, 
           ApplicationName = AppDomain.CurrentDomain.SetupInformation.ApplicationName, 
           LoaderOptimization = LoaderOptimization.MultiDomainHost 
          }; 
    
          // Create the child AppDomain 
          childDomain = AppDomain.CreateDomain("Timebox Domain", null, domainSetup); 
    
          // Create an instance of the timebox runtime child AppDomain 
          var timeboxRuntime = (ITimeboxRuntime)childDomain.CreateInstanceAndUnwrap(
           typeof(TimeboxRuntime).Assembly.FullName, typeof(TimeboxRuntime).FullName); 
    
          // Start the runtime, by passing it the function we're timboxing 
          Exception ex = null; 
          var timeoutOccurred = true; 
          var task = new Task(() => 
          { 
           ex = timeboxRuntime.Run(func); 
           timeoutOccurred = false; 
          }); 
    
          // start task, and wait for the alloted timespan. If the method doesn't finish 
          // by then, then we kill the childDomain and throw a TimeoutException 
          task.Start(); 
          task.Wait(_ts); 
    
          // if the timeout occurred then we throw the exception for the caller to handle. 
          if(timeoutOccurred) 
          { 
           throw new TimeoutException("The child domain timed out"); 
          } 
    
          // If no timeout occurred, then throw whatever exception was thrown 
          // by our child AppDomain, so that calling code "sees" the exception 
          // thrown by the code that it passes in. 
          if(ex != null) 
          { 
           throw ex; 
          } 
         } 
         finally 
         { 
          // kill the child domain whether or not the function has completed 
          if(childDomain != null) AppDomain.Unload(childDomain); 
         } 
        } 
    
        // don't strictly need this, but I prefer having an interface point to the proxy 
        private interface ITimeboxRuntime 
        { 
         Exception Run(Action action); 
        } 
    
        // Need to derive from MarshalByRefObject... proxy is returned across AppDomain boundary. 
        private class TimeboxRuntime : MarshalByRefObject, ITimeboxRuntime 
        { 
         public Exception Run(Action action) 
         { 
          try 
          { 
           // Nike: just do it! 
           action(); 
          } 
          catch(Exception e) 
          { 
           // return the exception to be thrown in the calling AppDomain 
           return e; 
          } 
          return null; 
         } 
        } 
    } 
    

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

    कारण मैं Thread या केवल Task रों के बजाय एक AppDomain साथ चला गया, क्योंकि वहाँ मनमाने ढंग से कोड के लिए Thread या Task रों समाप्त करने के लिए कोई बुलेट प्रूफ तरीका है [1] [2] [3]। आपकी आवश्यकताओं के लिए एक ऐपडोमेन, मेरे लिए सबसे अच्छा तरीका प्रतीत होता है।

  • +0

    जबकि मैं हमेशा उन्नत अनुमतियों का उपयोग नहीं कर सकता, यह एक बहुत ही साफ समाधान है। मैं शायद बिट्स और टुकड़ों में इसका उपयोग करूंगा। – Jerry

    +0

    यदि 'थ्रेड.एबॉर्ट() 'ऐसा कुछ है जिसे आप सहजता से उपयोग कर रहे हैं, तो यह वैसे ही काम करता है जैसे w/an' AppDomain'। 'Ex = timeboxRuntime.Run (func) 'के बजाय आप थ्रेड को कैप्चर कर सकते हैं (जैसे आपने अपने पोस्ट किए गए समाधान में किया था) और कार्य में' func()' को कॉल करें। बाद में, डोमेन को उतारने की बजाय, आप थ्रेड को रोक सकते हैं (यदि यह अभी भी चल रहा है)। इसे उन्नत अनुमतियों की आवश्यकता नहीं होगी। ** मैं अभी भी 'AppDomain' ** का उपयोग करने की अनुशंसा करता हूं, क्योंकि मैं हमेशा [एरिक लिपर्ट की सलाह] का पालन करता हूं (http://stackoverflow.com/questions/1559255/whats-wrong-with-using-thread-abort/1560567 # 1560567) :)। –

    2

    यहाँ समय समाप्ति के एक async कार्यान्वयन है:

    ... 
         private readonly semaphore = new SemaphoreSlim(1,1); 
    
        ... 
         // total time allowed here is 100ms 
         var tokenSource = new CancellationTokenSource(100); 
         try{ 
         await WorkMethod(parameters, tokenSource.Token); // work 
         } catch (OperationCancelledException ocx){ 
         // gracefully handle cancellations: 
         label.Text = "Operation timed out"; 
         } 
        ... 
    
        public async Task WorkMethod(object prm, CancellationToken ct){ 
         try{ 
         await sem.WaitAsync(ct); // equivalent to lock(object){...} 
         // synchronized work, 
         // call tokenSource.Token.ThrowIfCancellationRequested() or 
         // check tokenSource.IsCancellationRequested in long-running blocks 
         // and pass ct to other tasks, such as async HTTP or stream operations 
         } finally { 
         sem.Release(); 
         } 
        } 
    

    नहीं कि मैं इसे सलाह देने के लिए है, लेकिन आप अपने WorkMethod में Token के बजाय tokenSource गुजरती हैं और समय-समय पर tokenSource.CancelAfter(200) कर अधिक समय जोड़ने के लिए कर रहे हैं हो सकता है निश्चित रूप से आप ऐसी जगह पर नहीं हैं जो मृत-बंद हो सकता है (एक HTTP कॉल पर प्रतीक्षा कर रहा है) लेकिन मुझे लगता है कि यह बहुप्रचार के लिए एक गूढ़ दृष्टिकोण होगा।

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

    0

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

    संक्षेप में, ऐसा नहीं किया जा सकता है जिस तरह से मैं इसे करना चाहता था।

    हालांकि, मैंने पीटर के दृष्टिकोण को लिया है और मेरे कोड को थोड़ा उलझा दिया है। यह कुछ पठनीयता मुद्दों को पेश करता है, लेकिन मैंने टिप्पणियों और इस तरह के साथ उन्हें कम करने की कोशिश की है।

    public void MyMethod(...) 
    { 
    
    ... 
    
        // Placeholder for thread to kill if the action times out. 
        Thread threadToKill = null; 
        Action wrappedAction =() => 
        { 
         // Take note of the action's thread. We may need to kill it later. 
         threadToKill = Thread.CurrentThread; 
    
         ... 
         /* DO STUFF HERE */ 
         ... 
    
        }; 
    
        // Now, execute the action. We'll deal with the action timeouts below. 
        IAsyncResult result = wrappedAction.BeginInvoke(null, null); 
    
        // Set the timeout to 10 minutes. 
        if (result.AsyncWaitHandle.WaitOne(10 * 60 * 1000)) 
        { 
         // Everything was successful. Just clean up the invoke and get out. 
         wrappedAction.EndInvoke(result); 
        } 
        else 
        { 
         // We have timed out. We need to abort the thread!! 
         // Don't let it continue to try to do work. Something may be stuck. 
         threadToKill.Abort(); 
         throw new TimeoutException("This code block timed out"); 
        } 
    
    ... 
    } 
    

    जब से मैं प्रमुख खंड प्रति तीन या चार स्थानों में इस कर रहा हूँ, इस कठिन प्राप्त करता है से अधिक पढ़ने के लिए। हालांकि, यह काफी अच्छी तरह से काम करता है।

    +0

    इस कोड को डुप्लिकेट करने की कोई आवश्यकता नहीं है - आप इसे एक सहायक विधि में डाल सकते हैं जो 'एक्शन' को तर्क के रूप में लेता है, और 'एक्शन' को 'wrappedAction'' से कॉल करता है। ध्यान दें कि आपका कोड कॉलिंग थ्रेड को अवरुद्ध कर रहा है, और यह कोड लिखने की अनुशंसा की जाती है जो थ्रेड को रद्द करने के बजाय 'हिंसक रूप से' रद्द करने के बजाय रद्दीकरण के साथ सहयोग करता है। आधुनिक दृष्टिकोण 'टास्क' का उपयोग 'रद्दीकरण टोकन (स्रोत)' के साथ करना होगा (जो, आसानी से, तर्क के रूप में टाइमआउट ले सकता है)। –

    +1

    @ जेरी वन नोट: 'थ्रेड। एबॉर्ट() 'विवादास्पद है। [एरिक लिपर्ट के अनुसार] (http://stackoverflow.com/a/1560567/3960399), जो सी # पर काफी अधिकार है, इसे हर कीमत से बचा जाना चाहिए। –

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