कार्य की निरंतरता क्यों होती है। जब सभी को समान रूप से निष्पादित किया जाता है?


14

मैंने सिर्फ Task.WhenAll.NET कोर 3.0 पर चलने के दौरान विधि के बारे में एक उत्सुक अवलोकन किया । मैंने एक साधारण Task.Delayकार्य को एक तर्क के रूप में पारित किया Task.WhenAll, और मुझे उम्मीद थी कि लिपटे हुए कार्य मूल कार्य के लिए समान रूप से व्यवहार करेंगे। पर ये स्थिति नहीं है। मूल कार्य की निरंतरता को एसिंक्रोनस रूप से निष्पादित किया जाता है (जो वांछनीय है), और एकाधिक Task.WhenAll(task)आवरणों की निरंतरता को एक के बाद एक (जो अवांछनीय है) को एक साथ निष्पादित किया जाता है।

यहाँ इस व्यवहार का एक डेमो है। चार कार्यकर्ता कार्यों Task.Delayको पूरा करने के लिए समान कार्य की प्रतीक्षा कर रहे हैं , और फिर एक भारी गणना (एक द्वारा सिम्युलेटेड Thread.Sleep) के साथ जारी रखें ।

var task = Task.Delay(500);
var workers = Enumerable.Range(1, 4).Select(async x =>
{
    Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff}" +
        $" [{Thread.CurrentThread.ManagedThreadId}] Worker{x} before await");

    await task;
    //await Task.WhenAll(task);

    Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff}" +
        $" [{Thread.CurrentThread.ManagedThreadId}] Worker{x} after await");

    Thread.Sleep(1000); // Simulate some heavy CPU-bound computation
}).ToArray();
Task.WaitAll(workers);

यहाँ आउटपुट है। अलग-अलग थ्रेड्स (समानांतर में) के अनुसार चार निरंतरता चल रही है।

05:23:25.511 [1] Worker1 before await
05:23:25.542 [1] Worker2 before await
05:23:25.543 [1] Worker3 before await
05:23:25.543 [1] Worker4 before await
05:23:25.610 [4] Worker1 after await
05:23:25.610 [7] Worker2 after await
05:23:25.610 [6] Worker3 after await
05:23:25.610 [5] Worker4 after await

अब अगर मैं लाइन पर टिप्पणी करता हूं await taskऔर निम्नलिखित लाइन को अनसुना करता हूं await Task.WhenAll(task), तो आउटपुट काफी अलग है। सभी निरंतरता एक ही धागे में चल रही है, इसलिए गणनाओं को समानांतर नहीं किया गया है। प्रत्येक गणना पिछले एक के पूरा होने के बाद शुरू हो रही है:

05:23:46.550 [1] Worker1 before await
05:23:46.575 [1] Worker2 before await
05:23:46.576 [1] Worker3 before await
05:23:46.576 [1] Worker4 before await
05:23:46.645 [4] Worker1 after await
05:23:47.648 [4] Worker2 after await
05:23:48.650 [4] Worker3 after await
05:23:49.651 [4] Worker4 after await

आश्चर्यजनक रूप से यह तभी होता है जब प्रत्येक कार्यकर्ता एक अलग रैपर का इंतजार करता है। अगर मैं रैपर को परिभाषित करता हूं:

var task = Task.WhenAll(Task.Delay(500));

... और फिर awaitसभी श्रमिकों के अंदर एक ही कार्य, व्यवहार पहले मामले (अतुल्यकालिक निरंतरता) के समान है।

मेरा सवाल है: ऐसा क्यों हो रहा है? एक ही कार्य के अलग-अलग रैपरों को एक ही धागे में, समान रूप से निष्पादित करने के लिए क्या कारण हैं?

नोट: एक ही अजीब व्यवहार में परिणामों के Task.WhenAnyबजाय किसी कार्य को लपेटना Task.WhenAll

एक और अवलोकन: मुझे उम्मीद थी कि आवरण को लपेटने Task.Runसे निरंतरता अतुल्यकालिक होगी। लेकिन ऐसा नहीं हो रहा है। नीचे की पंक्ति की निरंतरता को अभी भी एक ही धागे में (समकालिक रूप से) निष्पादित किया जाता है।

await Task.Run(async () => await Task.WhenAll(task));

स्पष्टता: .NET कोर 3.0 प्लेटफॉर्म पर चल रहे कंसोल एप्लिकेशन में उपरोक्त अंतर देखे गए। .NET फ्रेमवर्क 4.8 पर, मूल कार्य या कार्य-आवरण की प्रतीक्षा करने में कोई अंतर नहीं है। दोनों मामलों में, एक ही धागे में निरंतरता को समान रूप से निष्पादित किया जाता है।


बस जिज्ञासु, अगर क्या होगा await Task.WhenAll(new[] { task });?
vasily.sib

1
मुझे लगता है कि इसकी वजह से अंदर शॉर्ट सर्किट हो रहा हैTask.WhenAll
माइकल रान्डेल

3
LinqPad दोनों वेरिएंट के लिए समान अपेक्षित दूसरा आउटपुट देता है ... आप समानांतर रन (कंसोल बनाम WinForms बनाम ..., .NET बनाम कोर, ..., फ्रेमवर्क संस्करण) प्राप्त करने के लिए किस वातावरण का उपयोग करते हैं?
एलेक्सी लेवेनकोव

1
मैं नेट कोर 3.0 और 3.1 पर इस व्यवहार नकल करने में सक्षम था, लेकिन प्रारंभिक बदलने के बाद ही Task.Delayसे 100करने के लिए 1000इसे पूरा नहीं किया जाता है, ताकि जब awaitएड।
स्टीफन क्ली

2
@BlueStrat अच्छा लगता है! यह निश्चित रूप से किसी भी तरह से संबंधित हो सकता है। दिलचस्प बात यह है कि मैं .NET फ्रेमवर्क 4.6, 4.6.1, 4.7.1, 4.7.2 और 4.8 पर Microsoft के कोड के गलत व्यवहार को पुन: पेश करने में विफल रहा । मुझे हर बार अलग थ्रेड आईडी मिलते हैं, जो कि सही व्यवहार है। यहां 4.7.2 पर एक फीलिंग चल रही है।
थियोडोर ज़ूलियास

जवाबों:


2

तो आपके पास एक ही कार्य चर की प्रतीक्षा में कई async विधियाँ हैं;

    await task;
    // CPU heavy operation

हां, ये निरंतरता taskपूर्ण होने पर श्रृंखला में बुलाई जाएगी । आपके उदाहरण में, प्रत्येक निरंतरता फिर अगले सेकंड के लिए धागा hogs।

यदि आप चाहते हैं कि प्रत्येक निरंतरता अतुल्यकालिक रूप से चले तो आपको कुछ इस तरह की आवश्यकता हो सकती है;

    await task;
    await Task.Yield().ConfigureAwait(false);
    // CPU heavy operation

ताकि आपके कार्य प्रारंभिक निरंतरता से वापस आ जाएं, और सीपीयू लोड को बाहर चलाने की अनुमति दें SynchronizationContext


उत्तर के लिए धन्यवाद जेरेमी। हां, Task.Yieldमेरी समस्या का अच्छा समाधान है। मेरा प्रश्न हालांकि यह क्यों हो रहा है, इस बारे में अधिक है, और वांछित व्यवहार को मजबूर करने के बारे में कम है।
थियोडोर जूलियास

यदि आप वास्तव में जानना चाहते हैं, तो स्रोत कोड यहां है; github.com/microsoft/referencesource/blob/master/mscorlib/…
जेरेमी लक्ष्मण

मेरी इच्छा है कि संबंधित कक्षाओं के स्रोत कोड का अध्ययन करके मेरे प्रश्न का उत्तर प्राप्त करना इतना आसान था। यह मुझे कोड समझने और क्या चल रहा है यह जानने में उम्र लगेगी!
थियोडोर ज़ूलियास

कुंजी से बचना है SynchronizationContext, ConfigureAwait(false)मूल कार्य पर एक बार कॉल करना पर्याप्त हो सकता है।
जेरेमी लक्ष्मण

यह एक कंसोल एप्लिकेशन है, और SynchronizationContext.Currentयह अशक्त है। लेकिन मैंने इसे सुनिश्चित करने के लिए जाँच की। मैंने लाइन ConfigureAwait(false)में जोड़ा awaitऔर इससे कोई फर्क नहीं पड़ा। प्रेक्षण पहले जैसे ही हैं।
थियोडोर ज़ूलियास

1

जब किसी कार्य का उपयोग करके बनाया जाता है Task.Delay(), तो इसके निर्माण के विकल्प के Noneबजाय सेट किया जाता है RunContinuationsAsychronously

यह .net ढांचे और .net कोर के बीच परिवर्तन को तोड़ने वाला हो सकता है। इसके बावजूद, यह आपके द्वारा देखे जा रहे व्यवहार की व्याख्या करता है। तुम भी स्रोत कोड है कि की खुदाई से इस की पुष्टि कर सकते Task.Delay()है अप Newing एक DelayPromiseजो डिफ़ॉल्ट कॉल Taskनिर्माता कोई निर्माण विकल्प निर्दिष्ट छोड़कर।


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

0

आपके कोड में, निम्न कोड आवर्ती निकाय से बाहर है।

var task = Task.Delay(100);

इसलिए हर बार जब आप निम्न कार्य करते हैं तो यह कार्य की प्रतीक्षा करेगा और इसे एक अलग थ्रेड में चलाएगा

await task;

लेकिन अगर आप निम्नलिखित taskको चलाते हैं, तो यह स्थिति की जाँच करेगा , इसलिए यह इसे एक थ्रेड में चलाएगा

await Task.WhenAll(task);

लेकिन अगर आप कार्य निर्माण को आगे बढ़ाते हैं तो WhenAllयह प्रत्येक कार्य को अलग थ्रेड में चलाएगा।

var task = Task.Delay(100);
await Task.WhenAll(task);

उत्तर के लिए धन्यवाद सीरादौफ। हालांकि आपका स्पष्टीकरण मेरे लिए बहुत संतोषजनक नहीं है। द्वारा लौटाया गया कार्य मूल की तरह Task.WhenAllएक नियमित है । दोनों कार्य किसी बिंदु पर पूरा हो रहे हैं, एक टाइमर घटना के परिणामस्वरूप मूल, और मूल कार्य के पूरा होने के परिणामस्वरूप समग्र। उनकी निरंतरताओं को अलग व्यवहार क्यों दिखाना चाहिए? एक कार्य किस पहलू से दूसरे में भिन्न है? Tasktask
थियोडोर ज़ूलियास
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.