2014-12-20 3 views
10

अगर मैं निम्नलिखित की तरह एक साधारण क्लास बनाने के लिए:एसिंक कीवर्ड संकलित करते समय एक गणनाकर्ता और अतिरिक्त संरचना क्यों उत्पन्न करता है?

public class TestClass 
{ 
    public Task TestMethod(int someParameter) 
    { 
     return Task.FromResult(someParameter); 
    } 

    public async Task TestMethod(bool someParameter) 
    { 
     await Task.FromResult(someParameter); 
    } 
} 

और NDepend भीतर यह जांच करते हैं, यह पता चलता है कि TestMethod एक bool लेने और async Task जा रहा है एक struct एक प्रगणक, प्रगणक राज्य मशीन के साथ के लिए उत्पन्न किया है और कुछ अतिरिक्त सामान।

enter image description here

क्यों संकलक एक struct उत्पन्न करता है async विधि के लिए एक प्रगणक के साथ TestClass+<TestMethod>d__0 कहा जाता है?

ऐसा लगता है कि वास्तविक विधि क्या उत्पन्न करती है उससे अधिक आईएल उत्पन्न करती है। इस उदाहरण में, संकलक मेरी कक्षा के लिए आईएल की 35 लाइनें उत्पन्न करता है, जबकि यह संरचना के लिए आईएल की 81 लाइनें उत्पन्न करता है। यह संकलित कोड की जटिलता में भी वृद्धि कर रहा है और कई नियमों के उल्लंघनों के लिए इसे ध्वजांकित करने के लिए एनडॉइंट का कारण बन रहा है।

+2

async/await में कोड [जेफ रिचटर के असिंक एन्युमेरेटर पैटर्न] पर आधारित है (http://channel9.msdn.com/Blogs/Charles/Jeffrey-Richter-and-his-AsyncEnumerator)। इस https://ndepend.uservoice.com/forums/226344- निर्भर-user-voice/suggestions/6375659-exclude-compiler-generated-code-by-default के लिए एक फिक्स का सुझाव देने वाली आवाज़ पहले से ही है। – Aron

+3

क्योंकि इस तरह async/await काम करता है। आप जो देखते हैं वह [कार्यान्वयन विस्तार] (http://msdn.microsoft.com/en-us/magazine/hh456402.aspx) सुविधा का है। –

+0

यह वास्तव में यह सुनिश्चित करने के महत्व को रेखांकित करता है कि जब आप इसकी आवश्यकता नहीं होती है तो आप अंधेरे से सभी जगहों पर एसिंक का उपयोग नहीं कर रहे हैं। मुझे लगता है कि मुझे उम्मीद थी कि यह अपेक्षाकृत अधिक है। –

उत्तर

2

ऐसा इसलिए है क्योंकि async और await कीवर्ड coroutines नामक किसी चीज़ के लिए सिंटैक्टिकल चीनी हैं।

एसिंक्रोनस विधियों के निर्माण का समर्थन करने के लिए कोई विशेष आईएल निर्देश नहीं हैं। इसके बजाए, एक एसिंक विधि किसी भी तरह की राज्य मशीन के रूप में देखी जा सकती है।

मैं इस उदाहरण के रूप में संभव के रूप में कम करने की कोशिश करेंगे:

[TestClass] 
public class AsyncTest 
{ 
    [TestMethod] 
    public async Task RunTest_1() 
    { 
     var result = await GetStringAsync(); 
     Console.WriteLine(result); 
    } 

    private async Task AppendLineAsync(StringBuilder builder, string text) 
    { 
     await Task.Delay(1000); 
     builder.AppendLine(text); 
    } 

    public async Task<string> GetStringAsync() 
    { 
     // Code before first await 
     var builder = new StringBuilder(); 
     var secondLine = "Second Line"; 

     // First await 
     await AppendLineAsync(builder, "First Line"); 

     // Inner synchronous code 
     builder.AppendLine(secondLine); 

     // Second await 
     await AppendLineAsync(builder, "Third Line"); 

     // Return 
     return builder.ToString(); 
    } 
} 

यह जैसा कि आप शायद करने के लिए इस्तेमाल हो गए कुछ async कोड है: पहली बार में हमारी GetStringAsync विधि तुल्यकालिक एक StringBuilder बनाता है, तो यह इंतजार कर रहा है कुछ असीमित तरीकों और अंत में यह परिणाम देता है। await कीवर्ड नहीं होने पर यह कैसे लागू किया जाएगा?

AsyncTest वर्ग के लिए निम्न कोड जोड़ें:

[TestMethod] 
public async Task RunTest_2() 
{ 
    var result = await GetStringAsyncWithoutAwait(); 
    Console.WriteLine(result); 
} 

public Task<string> GetStringAsyncWithoutAwait() 
{ 
    // Code before first await 
    var builder = new StringBuilder(); 
    var secondLine = "Second Line"; 

    return new StateMachine(this, builder, secondLine).CreateTask(); 
} 

private class StateMachine 
{ 
    private readonly AsyncTest instance; 
    private readonly StringBuilder builder; 
    private readonly string secondLine; 
    private readonly TaskCompletionSource<string> completionSource; 

    private int state = 0; 

    public StateMachine(AsyncTest instance, StringBuilder builder, string secondLine) 
    { 
     this.instance = instance; 
     this.builder = builder; 
     this.secondLine = secondLine; 
     this.completionSource = new TaskCompletionSource<string>(); 
    } 

    public Task<string> CreateTask() 
    { 
     DoWork(); 
     return this.completionSource.Task; 
    } 

    private void DoWork() 
    { 
     switch (this.state) 
     { 
      case 0: 
       goto state_0; 
      case 1: 
       goto state_1; 
      case 2: 
       goto state_2; 
     } 

     state_0: 
      this.state = 1; 

      // First await 
      var firstAwaiter = this.instance.AppendLineAsync(builder, "First Line") 
             .GetAwaiter(); 
      firstAwaiter.OnCompleted(DoWork); 
      return; 

     state_1: 
      this.state = 2; 

      // Inner synchronous code 
      this.builder.AppendLine(this.secondLine); 

      // Second await 
      var secondAwaiter = this.instance.AppendLineAsync(builder, "Third Line") 
              .GetAwaiter(); 
      secondAwaiter.OnCompleted(DoWork); 
      return; 

     state_2: 
      // Return 
      var result = this.builder.ToString(); 
      this.completionSource.SetResult(result); 
    } 
} 

तो स्पष्ट रूप से पहले await कीवर्ड से पहले कोड सिर्फ एक ही रहता है। बाकी सब कुछ एक राज्य मशीन में परिवर्तित हो जाता है जो आपके पिछले कोड को टुकड़े टुकड़े करने के लिए goto कथन का उपयोग करता है। हर बार प्रतीक्षा किए गए कार्यों में से एक पूरा हो जाता है, राज्य मशीन अगले चरण में आगे बढ़ती है।

यह उदाहरण स्पष्ट करने के लिए अतिसंवेदनशील है कि दृश्यों के पीछे क्या होता है। त्रुटि प्रबंधन और कुछ foreach जोड़ें- आपकी एसिंक विधि में लूप, और राज्य मशीन अधिक जटिल हो जाती है।

वैसे, सी # में एक और निर्माण है जो ऐसी चीज करता है: yield कीवर्ड। यह एक राज्य मशीन भी उत्पन्न करता है और कोड await उत्पादन के समान दिखता है।

आगे पढ़ने के लिए, this CodeProject पर देखें जो उत्पन्न राज्य मशीन में गहराई से दिखता है।

+0

लिंक के लिए धन्यवाद विवरण के उत्तर के लिए धन्यवाद, इससे वास्तव में इसे स्पष्ट करने में मदद मिली। मैं इस लिंक को भी देखना सुनिश्चित करूँगा –

+0

इस उदाहरण में, आपकी DoWork() विधि async स्थिति मशीन में .MoveNext() का सरलीकृत विविधता है? –

+0

यह सही है। माइनस अनुकूलन, रद्दीकरण, अपवाद हैंडलिंग, आंतरिक ढांचे के तरीके। – Frank

3

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

कंपाइलर से उत्पन्न भागों के बारे में अधिक जानकारी के लिए, Jon Skeet's blog series सबसे अच्छा स्रोत है।

+0

यह एक अच्छी श्रृंखला थी, लिंक –

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