2012-06-27 9 views
6

मैं सिमुलेशन सिस्टम पर काम कर रहा हूं कि, अन्य चीजों के साथ, अलग-अलग अनुरूपित समय चरणों में कार्यों को निष्पादित करने की अनुमति देता है। निष्पादन सिमुलेशन थ्रेड के संदर्भ में होता है, लेकिन, सिस्टम का उपयोग कर 'ऑपरेटर' के परिप्रेक्ष्य से, वे असीमित व्यवहार करना चाहते हैं। शुक्र है कि आसान 'async/await' कीवर्ड के साथ टीपीएल, यह काफी सरल बनाता है।अक्सर पुन: संक्रमित घटनाओं पर टीपीएल पूर्ण होने के लिए कुशल सिग्नलिंग कार्य

public Task CycleExecutedEvent() 
    { 
     lock (_cycleExecutedBroker) 
     { 
      if (!IsRunning) throw new TaskCanceledException("Simulation has been stopped"); 
      return _cycleExecutedBroker.RegisterForCompletion(CycleExecutedEventName); 
     } 
    } 

यह मूल रूप से एक नया TaskCompletionSource पैदा कर रही है और फिर एक टास्क लौटने: मैं इस तरह सिमुलेशन पर एक आदिम विधि है। इस कार्य का उद्देश्य सिमुलेशन पर नया 'ExecuteCycle' होने पर इसकी निरंतरता को निष्पादित करना है।

मैं तो इस तरह की कुछ विस्तार तरीकों:

public static async Task WaitForDuration(this ISimulation simulation, double duration) 
    { 
     double startTime = simulation.CurrentSimulatedTime; 
     do 
     { 
      await simulation.CycleExecutedEvent(); 
     } while ((simulation.CurrentSimulatedTime - startTime) < duration); 
    } 

    public static async Task WaitForCondition(this ISimulation simulation, Func<bool> condition) 
    { 
     do 
     { 
      await simulation.CycleExecutedEvent(); 
     } while (!condition()); 
    } 

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

मैंने अपना कोड प्रोफाइल किया है और मुझे पता चला है कि मेरे कुल CPU समय का लगभग 5.5% इन समापनों में खर्च किया जाता है, जिनमें से केवल 'नगण्य' प्रतिशत को 'सक्रिय' कोड में खर्च किया जाता है। ट्रिगरिंग स्थितियों के वैध होने की प्रतीक्षा करते समय प्रभावी ढंग से नए समय को पूरा करने में खर्च किया जाता है।

मेरा प्रश्न: मैं 'ऑपरेटर व्यवहार' लिखने के लिए एसिंक/प्रतीक्षा पैटर्न की सुविधा को बनाए रखने के दौरान यहां प्रदर्शन में सुधार कैसे कर सकता हूं? मुझे लगता है कि मुझे लाइटर-वेट और/या पुन: प्रयोज्य टास्क कॉम्प्लेशनसोर्स की तरह कुछ चाहिए, यह देखते हुए कि ट्रिगरिंग घटना इतनी बार होती है।


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


किसी को भी भविष्य में इस सवाल को ब्राउज़ कर के लिए, यहाँ रिवाज है awaiter मैं एक साथ रखा:

public sealed class CycleExecutedAwaiter : INotifyCompletion 
{ 
    private readonly List<Action> _continuations = new List<Action>(); 

    public bool IsCompleted 
    { 
     get { return false; } 
    } 

    public void GetResult() 
    { 
    } 

    public void OnCompleted(Action continuation) 
    { 
     _continuations.Add(continuation); 
    } 

    public void RunContinuations() 
    { 
     var continuations = _continuations.ToArray(); 
     _continuations.Clear(); 
     foreach (var continuation in continuations) 
      continuation(); 
    } 

    public CycleExecutedAwaiter GetAwaiter() 
    { 
     return this; 
    } 
} 

और सिम्युलेटर में:

private readonly CycleExecutedAwaiter _cycleExecutedAwaiter = new CycleExecutedAwaiter(); 

    public CycleExecutedAwaiter CycleExecutedEvent() 
    { 
     if (!IsRunning) throw new TaskCanceledException("Simulation has been stopped"); 
     return _cycleExecutedAwaiter; 
    } 

यह थोड़ा अजीब है, के रूप में प्रतीक्षाकर्ता कभी भी पूर्ण रिपोर्ट नहीं करता है, लेकिन आग पंजीकृत होने पर कॉल पूरा होने के लिए जारी है; फिर भी, यह इस एप्लिकेशन के लिए अच्छा काम करता है। इससे सीपीयू ओवरहेड 5.5% से 2.1% तक कम हो जाता है। इसकी संभावना अभी भी कुछ tweaking की आवश्यकता होगी, लेकिन यह मूल पर एक अच्छा सुधार है।

+1

अरे, मेरे सामने एक मिनट पहले अपने प्रश्न का उत्तर देना उचित नहीं है! :-) – svick

+0

@ एसविक, अभी तक इसका पूरी तरह उत्तर नहीं दिया गया है; मुझे अभी भी कस्टम प्रतीक्षाकर्ता बनाने का तरीका जानने की जरूरत है। :) लिंक के लिए धन्यवाद; वे काफी उपयोगी हैं। –

+0

@ डैन ब्रायंट: आपको अपने प्रश्न का उत्तर देना चाहिए ... अपने स्वयं के प्रश्न के बजाए उत्तर के रूप में। – user7116

उत्तर

5

await कीवर्ड Task एस पर काम नहीं करता है, यह प्रतीक्षा करने योग्य पैटर्न का पालन करने वाली किसी भी चीज़ पर काम करता है। विवरण के लिए, Stephen Toub's article await anything; देखें।

लघु संस्करण प्रकार एक विधि GetAwaiter() कि एक प्रकार है कि INotifyCompletion लागू करता है और यह भी IsCompleted संपत्ति और GetResult() विधि (void -returning, अगर await अभिव्यक्ति एक मूल्य नहीं होना चाहिए) है देता है करने के लिए किया है। उदाहरण के लिए, TaskAwaiter देखें।

यदि आप अपना खुद का इंतजार कर रहे हैं, तो आप TaskCompletionSource एस आवंटित करने के ओवरहेड से परहेज करते हुए हर बार एक ही ऑब्जेक्ट वापस कर सकते हैं।

+0

मैंने सुझाव दिया है कि मैंने एक कस्टम प्रतीक्षा की है; यह बहुत तेज़ है, हालांकि अभी भी सुधार के लिए कमरा है, मुझे यकीन है। मुख्य बात यह है कि मुझे इसके बारे में बग्स यह है कि यह 'लीकी' है (कक्षाओं के बाहर Awaiter इंस्टेंस को पकड़ सकता है और इसे अपेक्षित संदर्भ से बाहर ट्रिगर करने के लिए रनकंटिन्यूशन को कॉल कर सकता है।) –

+0

@DanBryant मुझे यकीन नहीं है कि आप इसके बारे में कुछ भी कर सकते हैं। यदि आप किसी को निरंतरता निर्धारित करने का तरीका देते हैं (जो आप चाहते हैं), तो वे हमेशा अपेक्षित संदर्भ के बाहर इसका उपयोग कर सकते हैं। – svick

0

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

मुझे लगता है कि मेरे उत्तर का सारांश है: "स्रोत" धागे में गणना को स्थानांतरित करके क्रॉस-थ्रेड मैसेजिंग की मात्रा को कम करने का प्रयास करें।

+0

यह वास्तव में थ्रेड सीमाओं को पार नहीं कर रहा है; क्योंकि कार्य पूर्णीकरण सिमुलेशन थ्रेड पर पूरा होने के रूप में चिह्नित किया गया है, सिमुलेशन थ्रेड पर सभी समापन निष्पादित होते हैं। इस मामले में async/await का उपयोग करने का पूरा बिंदु सभी कार्यों को उसी थ्रेड पर चलाना है, नियंत्रित 'उपज' के साथ प्रति सिमुलेट निष्पादन चक्र में केवल एक बार जांचना है। –

+0

ठीक है, अभी भी कार्य ओवरहेड है कि सरल घटनाएं नहीं होती हैं। अपने जीवन चक्र के दौरान उन्हें कई इंटरलॉक ऑपरेशंस की आवश्यकता होती है जो हार्डवेयर ताले और बहुत महंगे होते हैं। वे एक प्रणाली वैश्विक संसाधन हैं। – usr

1

यहाँ TaskCompletionSource

public sealed class ReusableAwaiter<T> : INotifyCompletion 
{ 
    private Action _continuation = null; 
    private T _result = default(T); 
    private Exception _exception = null; 

    public bool IsCompleted 
    { 
     get; 
     private set; 
    } 

    public T GetResult() 
    { 
     if (_exception != null) 
      throw _exception; 
     return _result; 
    } 

    public void OnCompleted(Action continuation) 
    { 
     if (_continuation != null) 
      throw new InvalidOperationException("This ReusableAwaiter instance has already been listened"); 
     _continuation = continuation; 
    } 

    /// <summary> 
    /// Attempts to transition the completion state. 
    /// </summary> 
    /// <param name="result"></param> 
    /// <returns></returns> 
    public bool TrySetResult(T result) 
    { 
     if (!this.IsCompleted) 
     { 
      this.IsCompleted = true; 
      this._result = result; 

      if (_continuation != null) 
       _continuation(); 
      return true; 
     } 
     return false; 
    } 

    /// <summary> 
    /// Attempts to transition the exception state. 
    /// </summary> 
    /// <param name="result"></param> 
    /// <returns></returns> 
    public bool TrySetException(Exception exception) 
    { 
     if (!this.IsCompleted) 
     { 
      this.IsCompleted = true; 
      this._exception = exception; 

      if (_continuation != null) 
       _continuation(); 
      return true; 
     } 
     return false; 
    } 

    /// <summary> 
    /// Reset the awaiter to initial status 
    /// </summary> 
    /// <returns></returns> 
    public ReusableAwaiter<T> Reset() 
    { 
     this._result = default(T); 
     this._continuation = null; 
     this._exception = null; 
     this.IsCompleted = false; 
     return this; 
    } 

    public ReusableAwaiter<T> GetAwaiter() 
    { 
     return this; 
    } 
} 

अनुकरण ReusableAwaiter की मेरी संस्करण यहाँ है और परीक्षण कोड है।

class Program 
{ 
    static readonly ReusableAwaiter<int> _awaiter = new ReusableAwaiter<int>(); 

    static void Main(string[] args) 
    { 
     Task.Run(() => Test()); 

     Console.ReadLine(); 
     _awaiter.TrySetResult(22); 
     Console.ReadLine(); 
     _awaiter.TrySetException(new Exception("ERR")); 

     Console.ReadLine(); 
    } 

    static async void Test() 
    { 

     int a = await AsyncMethod(); 
     Console.WriteLine(a); 
     try 
     { 
      await AsyncMethod(); 
     } 
     catch(Exception ex) 
     { 
      Console.WriteLine(ex.Message); 
     } 

    } 

    static ReusableAwaiter<int> AsyncMethod() 
    { 
     return _awaiter.Reset(); 
    } 

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