.NET में नियंत्रण के प्रवाह और उपज का इंतजार कैसे करें?


105

जैसा कि मैं yieldकीवर्ड को समझता हूं , अगर एक इट्रेटर ब्लॉक के अंदर से उपयोग किया जाता है, तो यह कॉलिंग कोड पर नियंत्रण का प्रवाह लौटाता है, और जब पुन: इट्रेटर को कॉल किया जाता है, तो यह वहीं छोड़ता है जहां इसे छोड़ा गया था।

इसके अलावा, awaitन केवल कैली का इंतजार करता है, बल्कि यह कॉल करने वाले को नियंत्रण लौटाता है, केवल उसे लेने के लिए जहां कॉलर awaitsने विधि छोड़ दी थी ।

दूसरे शब्दों में - कोई धागा नहीं है , और async और इंतजार की "संगामिति" नियंत्रण के चतुर प्रवाह के कारण एक भ्रम है, जिसका विवरण वाक्यविन्यास द्वारा छुपाया गया है।

अब, मैं एक पूर्व असेंबली प्रोग्रामर हूं और मैं इंस्ट्रक्टर पॉइंटर्स, स्टैक आदि से बहुत परिचित हूं और मुझे लगता है कि कंट्रोल (सबरूटीन, रिकर्सन, लूप्स, ब्रांच) के सामान्य प्रवाह कैसे काम करते हैं। लेकिन ये नए निर्माण - मुझे नहीं मिलते।

जब कोई awaitपहुँच जाता है, तो रनटाइम को कैसे पता चलता है कि कोड का कौन सा भाग आगे बढ़ना चाहिए? यह कैसे पता चलता है कि यह फिर से शुरू हो सकता है जहां इसे छोड़ दिया गया था, और यह कैसे याद करता है कि कहां है? वर्तमान कॉल स्टैक का क्या होता है, क्या यह किसी तरह से सहेजा जाता है? क्या होगा यदि कॉलिंग विधि awaits- से पहले अन्य विधि कॉल करती है - तो स्टैक ओवरराइट क्यों नहीं होता है? और कैसे पृथ्वी पर रनटाइम अपवाद और स्टैक खोलना के मामले में इस सब के माध्यम से अपना काम करेगा?

जब yieldपहुंच जाता है, तो रनटाइम उस बिंदु का ट्रैक कैसे रखता है जहां चीजों को उठाया जाना चाहिए? इटरेटर स्थिति कैसे संरक्षित है?


4
आप TryRoslyn ऑनलाइन संकलक में उत्पन्न कोड पर एक नज़र डाल सकते हैं
xanatos

1
आप जॉन स्कीट द्वारा Eduasync लेख श्रृंखला की जांच करना चाह सकते हैं ।
लियोनिद वासीलेव

संबंधित दिलचस्प पढ़ें: stackoverflow.com/questions/37419572/…
जेसन सी

जवाबों:


115

मैं नीचे आपके विशिष्ट प्रश्नों का उत्तर दूंगा, लेकिन आप केवल हमारे व्यापक लेखों को पढ़ने के लिए अच्छी तरह से करेंगे कि हमने कैसे उपज और प्रतीक्षा की।

https://blogs.msdn.microsoft.com/ericlippert/tag/continuation-passing-style/

https://blogs.msdn.microsoft.com/ericlippert/tag/iterators/

https://blogs.msdn.microsoft.com/ericlippert/tag/async/

इनमें से कुछ लेख अब पुराने हैं; उत्पन्न कोड कई मायनों में अलग है। लेकिन यह निश्चित रूप से आपको यह विचार देगा कि यह कैसे काम करता है।

इसके अलावा, यदि आप यह नहीं समझते हैं कि लैम्ब्डा को क्लोजर कक्षाओं के रूप में कैसे उत्पन्न किया जाता है, तो पहले समझें । यदि आपके पास लैम्बडास नहीं है, तो आप एसिंक्स के सिर या पूंछ नहीं बनाएंगे।

जब कोई प्रतीक्षा हो जाती है, तो रनटाइम को कैसे पता चलता है कि आगे किस कोड का निष्पादन करना चाहिए?

await के रूप में उत्पन्न होता है:

if (the task is not completed)
  assign a delegate which executes the remainder of the method as the continuation of the task
  return to the caller
else
  execute the remainder of the method now

वह मूल रूप से यह है। अद्वैत सिर्फ एक फैंसी वापसी है।

यह कैसे पता चलता है कि यह फिर से शुरू हो सकता है जहां इसे छोड़ दिया गया था, और यह कैसे याद करता है कि कहां है?

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

आपको पता है कि कोडांतरक में यह कैसे किया जाता है। फू के लिए एक सक्रियण रिकॉर्ड स्टैक पर धकेल दिया जाता है; इसमें स्थानीय लोगों के मूल्य शामिल हैं। कॉल के बिंदु पर foo में रिटर्न पता स्टैक पर धकेल दिया जाता है। जब बार किया जाता है, तो स्टैक पॉइंटर और इंस्ट्रक्शन पॉइंटर को फिर से सेट किया जाता है, जहां उन्हें होने की आवश्यकता होती है और जहां से छोड़ा गया है, वहां से फू चलता रहता है।

प्रतीक्षारत की निरंतरता बिल्कुल वैसी ही है, सिवाय इसके कि रिकॉर्ड को इस कारण से रखा जाए कि सक्रियता का क्रम ढेर नहीं बनता है

प्रतिनिधि जो कार्य की निरंतरता के रूप में देता है, उसमें (1) एक नंबर होता है, जो एक लुकअप टेबल का इनपुट होता है, जो इंस्ट्रक्टर पॉइंटर देता है, जिसे आपको आगे निष्पादित करने की आवश्यकता होती है, और (2) लोकल और टेम्परेरी के सभी मान।

वहाँ कुछ अतिरिक्त गियर है; उदाहरण के लिए, .NET में यह एक कोशिश ब्लॉक के बीच में शाखा करने के लिए अवैध है, इसलिए आप तालिका में एक कोशिश ब्लॉक के अंदर कोड का पता चिपका नहीं सकते हैं। लेकिन ये बहीखाता विवरण हैं। वैचारिक रूप से, सक्रियण रिकॉर्ड केवल ढेर पर चला जाता है।

वर्तमान कॉल स्टैक का क्या होता है, क्या यह किसी तरह से सहेजा जाता है?

वर्तमान सक्रियण रिकॉर्ड में प्रासंगिक जानकारी को पहले स्थान पर कभी नहीं रखा जाता है; यह गेट-गो से ढेर को आवंटित किया जाता है। (खैर, औपचारिक मापदंडों को स्टैक पर या रजिस्टरों में सामान्य रूप से पारित किया जाता है और फिर विधि शुरू होने पर एक ढेर स्थान पर कॉपी किया जाता है।)

कॉल करने वालों के सक्रियण रिकॉर्ड संग्रहीत नहीं हैं; प्रतीक्षा शायद उनके पास लौटने वाली है, याद रखें, इसलिए उन्हें सामान्य रूप से निपटाया जाएगा।

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

क्या होगा यदि कॉल करने की विधि प्रतीक्षा करने से पहले अन्य विधि कॉल करती है - तो स्टैक ओवरराइट क्यों नहीं किया जाता है?

वे विधि कॉल रिटर्न, और इसलिए उनके सक्रियण रिकॉर्ड अब प्रतीक्षा के बिंदु पर स्टैक पर नहीं हैं।

और कैसे पृथ्वी पर रनटाइम अपवाद और स्टैक के मामले में इस सब के माध्यम से अपना काम करेगा?

एक अनकहा अपवाद की स्थिति में, अपवाद को पकड़ा जाता है, कार्य के अंदर संग्रहीत किया जाता है, और जब कार्य के परिणाम को प्राप्त किया जाता है, तो उसे पुन: फेंक दिया जाता है।

उस सभी बहीखाते को याद करें जिसका मैंने पहले उल्लेख किया था? अपवाद शब्दार्थ अधिकार प्राप्त करना एक बहुत बड़ा दर्द था, मुझे आपको बताना चाहिए।

जब पैदावार हो जाती है, तो रनटाइम उस बिंदु का ट्रैक कैसे रखता है जहां चीजों को उठाया जाना चाहिए? इटरेटर स्थिति कैसे संरक्षित है?

उसी तरह। स्थानीय लोगों की स्थिति को ढेर पर ले जाया जाता है, और एक संख्या जो उस निर्देश का प्रतिनिधित्व करती है जिस पर MoveNextअगली बार इसे फिर से शुरू करना चाहिए जिसे स्थानीय लोगों के साथ संग्रहीत किया जाता है।

और फिर, यह सुनिश्चित करने के लिए कि पुनरावृत्तियों को सही तरीके से संभाला जाता है, इट्रेटर ब्लॉक में गियर का एक गुच्छा है।


1
प्रश्न लेखकों की पृष्ठभूमि (कोडांतरक एट अल) के कारण, यह संभवतः उल्लेखनीय है कि ये दोनों निर्माण बिना प्रबंधित स्मृति के लिए संभव नहीं हैं। एक बंद के जीवनकाल के समन्वय के प्रयास में प्रबंधित स्मृति के बिना निश्चित रूप से आप अपने बूटस्ट्रैप्स पर ट्रिपिंग करेंगे।
जिम

सभी पृष्ठ लिंक नहीं मिले (404)
Digital3D

आपके सभी लेख अब उपलब्ध नहीं हैं। क्या आप उन्हें फिर से पा सकते हैं?
माइकेल टर्किसिन

1
@ माइकलक्यूरिन: वे अभी भी इंटरनेट पर हैं; Microsoft वह स्थान रखता है जहाँ ब्लॉग संग्रह है। मैं धीरे-धीरे उन सभी को अपनी व्यक्तिगत साइट पर स्थानांतरित करूँगा, और जब मेरे पास समय हो तो इन लिंक को अपडेट करने का प्रयास करूंगा।
एरिक लिपिपर्ट

38

yield दो में से आसान है, तो चलो इसे जांचते हैं।

कहो हमारे पास:

public IEnumerable<int> CountToTen()
{
  for (int i = 1; i <= 10; ++i)
  {
    yield return i;
  }
}

यह थोड़ा संकलित हो जाता है जैसे अगर हम लिखेंगे:

// Deliberately use name that isn't valid C# to not clash with anything
private class <CountToTen> : IEnumerator<int>, IEnumerable<int>
{
    private int _i;
    private int _current;
    private int _state;
    private int _initialThreadId = CurrentManagedThreadId;

    public IEnumerator<CountToTen> GetEnumerator()
    {
        // Use self if never ran and same thread (so safe)
        // otherwise create a new object.
        if (_state != 0 || _initialThreadId != CurrentManagedThreadId)
        {
            return new <CountToTen>();
        }

        _state = 1;
        return this;
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    public int Current => _current;

    object IEnumerator.Current => Current;

    public bool MoveNext()
    {
        switch(_state)
        {
            case 1:
                _i = 1;
                _current = i;
                _state = 2;
                return true;
            case 2:
                ++_i;
                if (_i <= 10)
                {
                    _current = _i;
                    return true;
                }
                break;
        }
        _state = -1;
        return false;
    }

    public void Dispose()
    {
      // if the yield-using method had a `using` it would
      // be translated into something happening here.
    }

    public void Reset()
    {
        throw new NotSupportedException();
    }
}

इसलिए, एक हाथ से लिखे गए कार्यान्वयन के रूप में कुशल नहीं है IEnumerable<int>और IEnumerator<int>(जैसे कि हम संभवतः एक अलग _state, _iऔर _currentइस मामले में बेकार नहीं होंगे ) लेकिन खराब नहीं (नए का निर्माण करने के बजाय ऐसा करने के लिए सुरक्षित होने पर खुद को फिर से उपयोग करने की चाल। ऑब्जेक्ट अच्छा है), और बहुत जटिल- yieldमनोरंजक तरीकों से निपटने के लिए एक्स्टेंसिबल है ।

और बेशक

foreach(var a in b)
{
  DoSomething(a);
}

के समान है:

using(var en = b.GetEnumerator())
{
  while(en.MoveNext())
  {
     var a = en.Current;
     DoSomething(a);
  }
}

फिर उत्पन्न MoveNext()को बार-बार कहा जाता है।

asyncमामले काफी इसी सिद्धांत है, लेकिन अतिरिक्त जटिलता का एक सा है। एक अन्य उत्तर कोड से एक उदाहरण का पुन: उपयोग करने के लिए :

private async Task LoopAsync()
{
    int count = 0;
    while(count < 5)
    {
       await SomeNetworkCallAsync();
       count++;
    }
}

उत्पादन कोड की तरह:

private struct LoopAsyncStateMachine : IAsyncStateMachine
{
  public int _state;
  public AsyncTaskMethodBuilder _builder;
  public TestAsync _this;
  public int _count;
  private TaskAwaiter _awaiter;
  void IAsyncStateMachine.MoveNext()
  {
    try
    {
      if (_state != 0)
      {
        _count = 0;
        goto afterSetup;
      }
      TaskAwaiter awaiter = _awaiter;
      _awaiter = default(TaskAwaiter);
      _state = -1;
    loopBack:
      awaiter.GetResult();
      awaiter = default(TaskAwaiter);
      _count++;
    afterSetup:
      if (_count < 5)
      {
        awaiter = _this.SomeNetworkCallAsync().GetAwaiter();
        if (!awaiter.IsCompleted)
        {
          _state = 0;
          _awaiter = awaiter;
          _builder.AwaitUnsafeOnCompleted<TaskAwaiter, TestAsync.LoopAsyncStateMachine>(ref awaiter, ref this);
          return;
        }
        goto loopBack;
      }
      _state = -2;
      _builder.SetResult();
    }
    catch (Exception exception)
    {
      _state = -2;
      _builder.SetException(exception);
      return;
    }
  }
  [DebuggerHidden]
  void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine param0)
  {
    _builder.SetStateMachine(param0);
  }
}

public Task LoopAsync()
{
  LoopAsyncStateMachine stateMachine = new LoopAsyncStateMachine();
  stateMachine._this = this;
  AsyncTaskMethodBuilder builder = AsyncTaskMethodBuilder.Create();
  stateMachine._builder = builder;
  stateMachine._state = -1;
  builder.Start(ref stateMachine);
  return builder.Task;
}

यह अधिक जटिल है, लेकिन एक समान मूल सिद्धांत है। मुख्य अतिरिक्त जटिलता यह है कि अब GetAwaiter()इसका उपयोग किया जा रहा है। यदि किसी भी समय awaiter.IsCompletedजाँच की जाती है तो यह वापस आ जाता है trueक्योंकि टास्क awaitएड पहले ही पूरा हो जाता है (उदाहरण के लिए ऐसे मामले जहां यह समकालिक रूप से वापस आ सकता है) तो विधि राज्यों के माध्यम से चलती रहती है, लेकिन अन्यथा यह वेटर को कॉलबैक के रूप में स्थापित करता है।

बस इसके साथ जो होता है, वह वेटर पर निर्भर करता है, कॉलबैक को ट्रिगर करने के संदर्भ में (जैसे async I / O पूरा होने पर, थ्रेड पूरा करने पर चलने वाला कार्य) और किसी थ्रेड पर थ्रेडिंग के लिए या थ्रेड थ्रेड पर चलने के लिए क्या आवश्यकताएँ हैं। , क्या मूल कॉल से संदर्भ की आवश्यकता हो सकती है या नहीं और इतने पर। जो कुछ भी है, हालांकि उस वेटर में कुछ है जो इसमें कॉल करेगा MoveNextऔर यह या तो काम के अगले टुकड़े (अगले तक await) के साथ जारी रहेगा या समाप्त होगा और वापस Taskउसी स्थिति में लौटाएगा जब इसे लागू किया जा रहा है।


क्या आपने अपना अनुवाद करने के लिए समय लिया? O_O uao।
कॉफ़ीडेवेल्टर

4
@DarioOO पहला ऐसा काम जो मैं बहुत जल्दी कर सकता हूं, yieldजब हाथ में लुढ़कने से लेकर अनुवाद करने तक बहुत कुछ किया हो, तो ऐसा करने में लाभ होता है (आमतौर पर अनुकूलन के रूप में, लेकिन यह सुनिश्चित करना चाहते हैं कि शुरुआती बिंदु कंपाइलर-जनरेट के करीब हो इसलिए बुरी धारणाओं के माध्यम से कुछ भी नहीं बनता है)। दूसरे का उपयोग पहली बार एक अन्य उत्तर में किया गया था और उस समय मेरे अपने ज्ञान में कुछ अंतराल थे इसलिए मैंने कोड को हाथ से डिकम्पोज करके उस उत्तर को प्रदान करते हुए स्वयं को लाभान्वित किया।
जॉन हैना

13

पहले से ही यहाँ महान जवाब के एक टन हैं; मैं सिर्फ कुछ दृष्टिकोण साझा करने जा रहा हूं जो मानसिक मॉडल बनाने में मदद कर सकते हैं।

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

दूसरा, awaitकाफी सरल अनुक्रम में अनुवादित है; मुझे लुसियन का विवरण पसंद है , जो शब्दों में बहुत अधिक है "अगर प्रतीक्षा पहले से ही पूरी हो गई है, तो परिणाम प्राप्त करें और इस पद्धति को जारी रखें; अन्यथा, इस पद्धति की स्थिति को बचाएं और वापस लौटें"। (मैं अपने asyncपरिचय में बहुत समान शब्दावली का उपयोग करता हूं )।

जब कोई प्रतीक्षा हो जाती है, तो रनटाइम को कैसे पता चलता है कि आगे किस कोड का निष्पादन करना चाहिए?

उस शेष के लिए कॉलबैक के रूप में विधि का शेष मौजूद है (कार्यों के मामले में, ये कॉलबैक निरंतर हैं)। जब प्रतीक्षा पूरी हो जाती है, तो यह अपने कॉलबैक को आमंत्रित करता है।

ध्यान दें कि कॉल स्टैक सहेजा नहीं गया है और पुनर्स्थापित नहीं किया गया है; कॉलबैक को सीधे लागू किया जाता है। ओवरलैप्ड I / O के मामले में, वे सीधे थ्रेड पूल से मंगवाए जाते हैं।

वे कॉलबैक विधि को सीधे निष्पादित कर सकते हैं, या वे इसे कहीं और चलाने के लिए शेड्यूल कर सकते हैं (उदाहरण के लिए, यदि awaitकब्जा किया गया UI SynchronizationContextऔर I / O थ्रेड पूल पर पूरा हो गया है)।

यह कैसे पता चलता है कि यह फिर से शुरू हो सकता है जहां इसे छोड़ दिया गया था, और यह कैसे याद करता है कि कहां है?

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

कॉलबैक कर रहे हैं नहीं एक विशेष धागे चलाने के लिए, और वे करते नहीं उनके callstack बहाल की है।

वर्तमान कॉल स्टैक का क्या होता है, क्या यह किसी तरह से सहेजा जाता है? क्या होगा यदि कॉल करने की विधि प्रतीक्षा करने से पहले अन्य विधि कॉल करती है - तो स्टैक ओवरराइट क्यों नहीं किया जाता है? और कैसे पृथ्वी पर रनटाइम अपवाद और स्टैक के मामले में इस सब के माध्यम से अपना काम करेगा?

कॉलस्टैक को पहले स्थान पर सहेजा नहीं गया है; यह आवश्यक नहीं है।

सिंक्रोनस कोड के साथ, आप एक कॉल स्टैक के साथ समाप्त कर सकते हैं जिसमें आपके सभी कॉलर्स शामिल हैं, और रनटाइम जानता है कि उस का उपयोग करके कहां लौटना है।

एसिंक्रोनस कोड के साथ, आप कॉलबैक पॉइंटर्स के एक समूह के साथ समाप्त हो सकते हैं - कुछ I / O ऑपरेशन में निहित है जो asyncअपने कार्य को पूरा करता है, जो एक asyncविधि को फिर से शुरू कर सकता है जो अपने कार्य को पूरा करता है, जो एक विधि को फिर से शुरू कर सकता है जो अपना कार्य समाप्त करता है, आदि।

तो, तुल्यकालिक कोड Aकॉलिंग Bकॉलिंग के साथ C, आपका कॉलस्टैक इस तरह दिख सकता है:

A:B:C

जबकि अतुल्यकालिक कोड कॉलबैक (पॉइंटर्स) का उपयोग करता है:

A <- B <- C <- (I/O operation)

जब पैदावार हो जाती है, तो रनटाइम उस बिंदु का ट्रैक कैसे रखता है जहां चीजों को उठाया जाना चाहिए? इटरेटर स्थिति कैसे संरक्षित है?

वर्तमान में, बल्कि अक्षम रूप से। :)

यह किसी भी अन्य लैम्ब्डा की तरह काम करता है - चर जीवनकाल को बढ़ाया जाता है और संदर्भ को एक राज्य वस्तु में रखा जाता है जो स्टैक पर रहता है। सभी गहरे स्तर के विवरणों के लिए सबसे अच्छा संसाधन जॉन स्कीट की एडुआसेन्स सीरीज़ है


7

yieldऔर awaitदोनों प्रवाह नियंत्रण के साथ काम करते समय, दो पूरी तरह से अलग चीजें हैं। इसलिए मैं उनसे अलग से निपटूंगा।

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

foreach(var item in mything.items()) {
    dosomething(item);
}

उत्पन्न कोड कुछ इस तरह दिखता है:

var i = mything.items();
while(i.MoveNext()) {
    dosomething(i.Current);
}

Mything.items () के कार्यान्वयन के अंदर राज्य-मशीन कोड का एक गुच्छा है जो लूप का एक "स्टेप" करेगा और फिर वापस आ जाएगा। इसलिए जब आप इसे एक साधारण लूप जैसे स्रोत में लिखते हैं, तो हुड के नीचे यह एक साधारण लूप नहीं है। तो संकलक प्रवंचना। यदि आप स्वयं को देखना चाहते हैं, तो ILDASM या ILSpy या इसी तरह के उपकरणों को बाहर निकालें और देखें कि उत्पन्न IL कैसा दिखता है। यह शिक्षाप्रद होना चाहिए।

asyncऔर await, दूसरी ओर, मछली की एक पूरी अन्य केतली हैं। इंतजार है, अमूर्त में, एक तुल्यकालन आदिम। यह सिस्टम को यह बताने का एक तरीका है "मैं तब तक जारी नहीं रख सकता जब तक कि ऐसा नहीं किया जाता।" लेकिन, जैसा कि आपने उल्लेख किया है, हमेशा एक थ्रेड शामिल नहीं होता है।

क्या है इसमें शामिल कुछ एक तुल्यकालन संदर्भ कहा जाता है। वहाँ हमेशा एक चारों ओर लटका रहता है। उनके लिए सिंक्रनाइज़ेशन संदर्भ का काम उन कार्यों को शेड्यूल करना है, जिन पर इंतजार किया जा रहा है और उनकी निरंतरता है।

जब आप कहते हैं await thisThing(), एक दो चीजें होती हैं। एक async विधि में, संकलक वास्तव में इस विधि को छोटे टुकड़ों में काटता है, प्रत्येक chunk "प्रतीक्षा से पहले" खंड और "प्रतीक्षा के बाद" (या निरंतरता) अनुभाग में होता है। जब वेट निष्पादित होता है, तो कार्य का इंतजार किया जा रहा है, और निम्नलिखित निरंतरता - दूसरे शब्दों में, फ़ंक्शन के बाकी - को सिंक्रनाइज़ेशन संदर्भ में पास किया जाता है। संदर्भ कार्य को शेड्यूल करने का ध्यान रखता है, और जब यह संदर्भ समाप्त हो जाता है, तो निरंतरता को चलाता है, जो भी वापसी मान चाहता है।

जब तक यह सामान शेड्यूल करता है तब तक सिंक संदर्भ जो कुछ भी करना चाहता है वह करने के लिए स्वतंत्र है। यह थ्रेड पूल का उपयोग कर सकता है। यह प्रति कार्य एक सूत्र बना सकता है। यह उन्हें सिंक्रोनाइज़ कर सकता है। विभिन्न वातावरण (ASP.NET बनाम WPF) विभिन्न सिंक संदर्भ कार्यान्वयन प्रदान करते हैं जो कि उनके वातावरण के लिए सबसे अच्छा है के आधार पर अलग-अलग चीजें करते हैं।

(बोनस: कभी सोचा है कि .ConfigurateAwait(false)यह क्या करता है? यह सिस्टम को वर्तमान सिंक संदर्भ (आमतौर पर आपके प्रोजेक्ट प्रकार के आधार पर - उदाहरण के लिए WPF बनाम ASP.NET) का उपयोग नहीं करने के लिए कह रहा है और इसके बजाय डिफ़ॉल्ट का उपयोग करें, जो थ्रेड पूल का उपयोग करता है)।

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

PS डिफ़ॉल्ट सिंक संदर्भों के अस्तित्व में एक अपवाद है - कंसोल ऐप्स में डिफ़ॉल्ट सिंक संदर्भ नहीं है। बहुत अधिक जानकारी के लिए स्टीफन टूब के ब्लॉग की जाँच करें । यह एक महान जगह के बारे में जानकारी के लिए देखने के लिए है asyncऔर awaitसामान्य रूप में।


1
"यह सिस्टम को डिफ़ॉल्ट सिंक संदर्भ का उपयोग नहीं करने के लिए कह रहा है और इसके बजाय डिफ़ॉल्ट एक का उपयोग करें, जो थ्रेड पूल का उपयोग करता है" क्या आप इससे स्पष्ट कर सकते हैं कि आपका इससे क्या मतलब है? "डिफ़ॉल्ट का उपयोग न करें, डिफ़ॉल्ट का उपयोग करें"
Kroltan

3
क्षमा करें, मेरी शब्दावली मिश्रित है, मैं पोस्ट ठीक कर दूंगा। मूल रूप से, आप जिस वातावरण में हैं उसके लिए डिफ़ॉल्ट का उपयोग न करें, .NET (यानी थ्रेड पूल) के लिए डिफ़ॉल्ट एक का उपयोग करें।
क्रिस टावर्स

बहुत सरल, समझने में सक्षम था, आपको मेरा वोट मिला :)
एहसान सज्जाद

4

आम तौर पर, मैं सीआईएल को देख कर पुनर्मिलन करूंगा, लेकिन इन के मामले में, यह एक गड़बड़ है।

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

yieldएक पुराना और सरल कथन है, और यह एक बुनियादी राज्य मशीन के लिए एक वाक्यात्मक चीनी है। लौटने की एक विधि IEnumerable<T>या IEnumerator<T>इसमें एक शामिल हो सकता है yield, जो तब विधि को राज्य मशीन कारखाने में बदल देता है। एक बात जो आपको ध्यान देनी चाहिए वह यह है कि इस विधि में कोई भी कोड उस समय नहीं चलाया जाता है जब आप उसे कॉल करते हैं, अगर कोई yieldअंदर है। कारण यह है कि आप जो कोड लिखते हैं वह IEnumerator<T>.MoveNextविधि के लिए अनुवादित होता है , जो उस स्थिति की जांच करता है और कोड का सही भाग चलाता है। yield return x;तो कुछ करने के लिए बदल जाता हैthis.Current = x; return true;

यदि आप कुछ प्रतिबिंब करते हैं, तो आप आसानी से निर्मित राज्य मशीन और उसके खेतों (कम से कम राज्य के लिए और स्थानीय लोगों के लिए) का निरीक्षण कर सकते हैं। यदि आप फ़ील्ड बदलते हैं तो आप इसे रीसेट भी कर सकते हैं।

awaitप्रकार की लाइब्रेरी से थोड़े से समर्थन की आवश्यकता होती है, और कुछ अलग तरीके से काम करता है। यह एक तर्क Taskया Task<T>तर्क लेता है, या तो कार्य पूरा होने पर इसके मूल्य पर परिणाम देता है, या एक निरंतरता को पंजीकृत करता है Task.GetAwaiter().OnCompletedasync/ awaitप्रणाली के पूर्ण कार्यान्वयन को समझाने में बहुत लंबा समय लगेगा, लेकिन यह भी उतना रहस्यमय नहीं है। यह एक राज्य मशीन भी बनाता है और इसे OnCompleted की निरंतरता के साथ पास करता है । यदि कार्य पूरा हो गया है, तो यह निरंतरता में अपने परिणाम का उपयोग करता है। वेटर का कार्यान्वयन यह तय करता है कि निरंतरता को कैसे लागू किया जाए। आमतौर पर यह कॉलिंग थ्रेड के सिंक्रनाइज़ेशन संदर्भ का उपयोग करता है।

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

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


5
साइड नोट : awaitएक कार्य लेने के लिए नहीं है । के साथ कुछ भी INotifyCompletion GetAwaiter()पर्याप्त है। कैसे foreachजरूरत नहीं है के IEnumerableसाथ थोड़ा सा , के साथ कुछ भी IEnumerator GetEnumerator()पर्याप्त है।
IllidanS4 मोनिका को
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.