जब थ्रेड थोड़ी देर लूप के अंदर किसी कार्य की प्रतीक्षा करता है तो वास्तव में क्या होता है?


10

थोड़ी देर के लिए C # के async / प्रतीक्षा पैटर्न से निपटने के बाद, मुझे अचानक यह पता चला कि मुझे वास्तव में यह नहीं पता है कि निम्नलिखित कोड में क्या होता है:

async void MyThread()
{
    while (!_quit)
    {
        await GetWorkAsync();
    }
}

GetWorkAsync()Taskनिरंतरता निष्पादित होने पर एक थ्रेड स्विच का कारण हो सकता है या नहीं करने के लिए मान लिया जाता है।

अगर उलझन पाश के अंदर नहीं होती तो मैं भ्रमित नहीं होता। मैं स्वाभाविक रूप से उम्मीद करूंगा कि बाकी विधि (यानी निरंतरता) संभवतः दूसरे धागे पर निष्पादित होगी, जो ठीक है।

हालांकि, एक लूप के अंदर, "बाकी विधि" की अवधारणा मेरे लिए थोड़ी धूमिल हो जाती है।

यदि थ्रेड निरंतरता पर स्विच किया जाता है तो बनाम "लूप के बाकी" का क्या होता है अगर इसे स्विच नहीं किया जाता है? लूप के अगले पुनरावृत्ति को किस धागे पर निष्पादित किया जाता है?

मेरी टिप्पणियों से पता चलता है (निर्णायक रूप से सत्यापित नहीं) कि प्रत्येक पुनरावृत्ति एक ही धागे (मूल एक) पर शुरू होती है जबकि निरंतरता दूसरे पर चलती है। क्या यह वास्तव में हो सकता है? यदि हाँ, तो क्या यह अनपेक्षित समानांतरवाद की एक डिग्री है जिसे गेटवॉर्क्ससिनस्टैंड विधि के दृश्य-ए-विज़ थ्रेड-सुरक्षा के लिए जिम्मेदार होना चाहिए?

अद्यतन: मेरा प्रश्न डुप्लिकेट नहीं है, जैसा कि कुछ ने सुझाव दिया है। while (!_quit) { ... }कोड पैटर्न केवल अपने वास्तविक कोड का सरलीकरण है। वास्तव में, मेरा धागा एक लंबे समय तक रहने वाला लूप है जो नियमित अंतराल (हर 5 सेकंड में डिफ़ॉल्ट रूप से) पर अपने काम की वस्तुओं की इनपुट कतार को संसाधित करता है। सैंपल कोड द्वारा सुझाई गई वास्तविक वास्तविक स्थिति की जांच भी एक साधारण फील्ड चेक नहीं है, बल्कि एक इवेंट हैंडल चेक है।



1
यह भी देखें कि .NET में नियंत्रण के प्रवाह को कैसे उपज और प्रतीक्षा करें? कैसे यह सब एक साथ तार पर कुछ महान जानकारी के लिए।
जॉन वू

@ जॉन वू: मैंने अभी तक एसओ धागा नहीं देखा है। दिलचस्प जानकारी के बहुत सारे सोने की डली वहाँ। धन्यवाद!
aoven

जवाबों:


6

आप वास्तव में Try Roslyn पर देख सकते हैं । आपकी प्रतीक्षा विधि फिर void IAsyncStateMachine.MoveNext()से उत्पन्न async वर्ग पर लिखी जाती है ।

आप जो देखेंगे वह कुछ इस तरह है:

            if (this.state != 0)
                goto label_2;
            //set up the state machine here
            label_1:
            taskAwaiter.GetResult();
            taskAwaiter = default(TaskAwaiter);
            label_2:
            if (!OuterClass._quit)
            {
               taskAwaiter = GetWorkAsync().GetAwaiter();
               //state machine stuff here
            }
            goto label_1;

असल में, इससे कोई फर्क नहीं पड़ता कि आप किस धागे पर हैं; राज्य मशीन अपने लूप को एक समान / गोटो संरचना के साथ बदलकर ठीक से फिर से शुरू कर सकती है।

कहा गया है कि, async तरीके जरूरी नहीं कि एक अलग धागे पर अमल करें। एरिक लिपर्ट का स्पष्टीकरण देखें "यह जादू नहीं है" यह समझाने के लिए कि आप async/awaitकेवल एक धागे पर कैसे काम कर सकते हैं ।


2
मुझे लगता है कि कंपाइलर मेरे async कोड पर पुनर्लेखन की सीमा को कम करके आंका जा रहा है। संक्षेप में, पुनर्लेखन के बाद कोई "लूप" नहीं है! वह मेरे लिए गायब हिस्सा था। बहुत बढ़िया और साथ ही 'ट्राय रोसलिन' लिंक के लिए धन्यवाद!
बुनी

गोटो मूल लूप निर्माण है। आइए इसे भूलते हैं।

2

सबसे पहले, Servy ने इसी तरह के प्रश्न के उत्तर में कुछ कोड लिखा है, जिस पर यह उत्तर आधारित है:

/programming/22049339/how-to-create-a-cancellable-task-loop

सर्व के उत्तर में ContinueWith()टीपीएल निर्माणों asyncऔर awaitखोजशब्दों के स्पष्ट उपयोग के बिना एक समान लूप शामिल है ; इसलिए अपने प्रश्न का उत्तर देने के लिए, विचार करें कि जब आपके लूप का उपयोग करके अनियंत्रित किया जाता है तो आपका कोड क्या दिख सकता हैContinueWith()

    private static Task GetWorkWhileNotQuit()
    {
        var tcs = new TaskCompletionSource<bool>();

        Task previous = Task.FromResult(_quit);
        Action<Task> continuation = null;
        continuation = t =>
        {
            if (!_quit)
            {
                previous = previous.ContinueWith(_ => GetWorkAsync())
                    .Unwrap()
                    .ContinueWith(_ => previous.ContinueWith(continuation));
            }
            else
            {
                tcs.SetResult(_quit);
            }
        };
        previous.ContinueWith(continuation);
        return tcs.Task;
    }

यह आपके सिर को चारों ओर लपेटने में कुछ समय लेता है, लेकिन सारांश में:

  • continuation"वर्तमान पुनरावृत्ति" के लिए एक बंद का प्रतिनिधित्व करता है
  • previous"पिछली पुनरावृत्ति"Task की स्थिति का प्रतिनिधित्व करता है (यानी यह जानता है कि 'चलना' समाप्त हो गया है और अगले एक से शुरू करने के लिए उपयोग किया जाता है ..)
  • मान लिया GetWorkAsync()जाता है Taskकि, इसका अर्थ है कि 'आंतरिक कार्य' (यानी वास्तविक परिणाम ) प्राप्त करने के लिए कॉल ContinueWith(_ => GetWorkAsync())आएगी ।Task<Task>Unwrap()GetWorkAsync()

इसलिए:

  1. प्रारंभ में कोई पिछली पुनरावृत्ति नहीं होती है , इसलिए इसे केवल एक मान दिया जाता है Task.FromResult(_quit) - इसका राज्य शुरू होता है Task.Completed == true
  2. continuationका उपयोग कर पहली बार के लिए चलाया जाता हैprevious.ContinueWith(continuation)
  3. continuationबंद के अपडेट previousके पूरा राज्य को प्रतिबिंबित करने के_ => GetWorkAsync()
  4. जब _ => GetWorkAsync()पूरा हो जाता है, तो यह "जारी रहता है" _previous.ContinueWith(continuation)- यानी continuationलंबोदर को फिर से कॉल करना
    • स्पष्ट रूप से इस बिंदु पर, previousराज्य के साथ अद्यतन किया गया है _ => GetWorkAsync()इसलिए continuationलैंबडा को GetWorkAsync()रिटर्न के समय कहा जाता है।

continuationलैम्ब्डा हमेशा के राज्य की जाँच करता है _quit, इसलिए यदि _quit == falseतो वहाँ कोई और अधिक निरंतरता रहे हैं, और TaskCompletionSourceके मान पर सेट हो जाता है _quit, और सब कुछ पूरा हो गया है।

निरंतरता के बारे में आपके अवलोकन के लिए एक अलग थ्रेड में निष्पादित होने के संबंध में, वह ऐसा कुछ नहीं है जो async/ awaitकीवर्ड आपके लिए करेगा, जैसा कि इस ब्लॉग के अनुसार "कार्य हैं (अभी भी) थ्रेड नहीं हैं और एसिंक्स समानांतर नहीं हैं" । - https://blogs.msdn.microsoft.com/benwilli/2015/09/10/tasks-are-still-not-threads-and-async-is-not-parallel/

मेरा सुझाव है कि यह वास्तव में GetWorkAsync()थ्रेडिंग और थ्रेड सुरक्षा के संबंध में आपकी विधि पर एक करीब से ध्यान देने योग्य है । यदि आपके डायग्नोस्टिक्स यह बता रहे हैं कि यह आपके द्वारा दोहराए गए async / प्रतीक्षा कोड के परिणाम के रूप में एक अलग थ्रेड पर निष्पादित हो रहा है, तो उस पद्धति से संबंधित या संबंधित किसी अन्य चीज़ के लिए एक नया थ्रेड उत्पन्न होना चाहिए। (यदि यह अप्रत्याशित है, तो शायद .ConfigureAwaitकहीं है?)


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

@aoven अच्छा बिंदु - मैंने विभिन्न प्रकारों पर विचार नहीं किया SynchronizationContext- यह निश्चित रूप से महत्वपूर्ण है क्योंकि .ContinueWith()निरंतरता को भेजने के लिए SynchronizationContext का उपयोग करता है; यह वास्तव में आपके द्वारा देखे जाने वाले व्यवहार की व्याख्या करेगा यदि awaitथ्रेडपूल थ्रेड या ASP.NET थ्रेड पर कहा जाता है। एक निरंतरता निश्चित रूप से उन मामलों में एक अलग धागे को भेजा जा सकता है। दूसरी ओर, awaitएक एकल-थ्रेडेड संदर्भ जैसे कि WPF डिस्पैचर या Winforms संदर्भ पर कॉल करना यह सुनिश्चित करने के लिए पर्याप्त होना चाहिए कि निरंतरता मूल पर होती है। धागा
बेन कॉटरेल
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.