2015-11-20 6 views
12

मैं हाल ही में एसिंक/प्रतीक्षा के बारे में पढ़ रहा था और मैं इस तथ्य से उलझन में हूं कि कई लेख/पोस्ट मैं पढ़ रहा था कि एसिंक प्रतीक्षा (Example) का उपयोग करते समय नया धागा नहीं बनाया गया है।async/अलग थ्रेड आईडी

मैं इसे निम्नलिखित कोड का

class Program 
    { 
     static void Main(string[] args) 
     { 
      Console.WriteLine("Main: " + Thread.CurrentThread.ManagedThreadId); 
      MainAsync(args).Wait(); 
      Console.WriteLine("Main End: " + Thread.CurrentThread.ManagedThreadId); 

      Console.ReadKey(); 
     } 


     static async Task MainAsync(string[] args) 
     { 
      Console.WriteLine("Main Async: " + Thread.CurrentThread.ManagedThreadId); 

      await thisIsAsync(); 
     } 

     private static async Task thisIsAsync() 
     { 
      Console.WriteLine("thisIsAsyncStart: " + Thread.CurrentThread.ManagedThreadId); 
      await Task.Delay(1); 
      Console.WriteLine("thisIsAsyncEnd: " + Thread.CurrentThread.ManagedThreadId); 

     } 
    } 

आउटपुट का परीक्षण करने के लिए एक सरल सांत्वना एप्लिकेशन बनाया है है:

Main: 8 
Main Async: 8 
thisIsAsyncStart: 8 
thisIsAsyncEnd: 9 
Main End: 8 

मैं बात को अनदेखा कर रहा हूँ, या thisIsAsyncEnd अन्य की तुलना में अलग थ्रेड ID रहा है कार्रवाई?

संपादित करें:

मैं कोड के रूप में await Task.Delay(1) लिए नीचे दिए गए जवाब में सुझाव दिया, लेकिन मैं अभी भी एक ही परिणाम देख रहा हूँ अद्यतन किया है।

नीचे जवाब से उद्धरण:

Rather, it enables the method to be split into multiple pieces, some of which may run asynchronously 

मैं कहाँ asynchronously हिस्सा रन करता है पता करने के लिए, अगर वहाँ बनाया कोई अन्य सूत्र हैं करना चाहते हैं? यदि यह एक ही थ्रेड पर चलता है, तो इसे लंबे I/O अनुरोध के कारण इसे अवरुद्ध नहीं करना चाहिए, या कंपाइलर उस क्रिया को किसी अन्य थ्रेड पर ले जाने के लिए पर्याप्त स्मार्ट है, यदि यह बहुत लंबा लगता है, और सभी के बाद एक नया धागा उपयोग किया जाता है?

उत्तर

17

मैं सुझाव है कि आप async और await कीवर्ड की समझ के लिए मेरे async intro पोस्ट पढ़ें। विशेष रूप से, await (डिफ़ॉल्ट रूप से) एक "संदर्भ" पर कब्जा करेगा और उस संदर्भ का उपयोग अपनी असीमित विधि को फिर से शुरू करने के लिए करेगा। यह "संदर्भ" वर्तमान SynchronizationContext (या TaskScheduler है, यदि कोई SynchronzationContext नहीं है)।

मैं जानना चाहता हूं कि असीमित रूप से भाग कहाँ होता है, यदि कोई अन्य थ्रेड नहीं बनाया गया है? यदि यह एक ही थ्रेड पर चलता है, तो इसे लंबे I/O अनुरोध के कारण इसे अवरुद्ध नहीं करना चाहिए, या कंपाइलर उस क्रिया को किसी अन्य थ्रेड पर ले जाने के लिए पर्याप्त स्मार्ट है, यदि यह बहुत लंबा लगता है, और सभी के बाद एक नया धागा उपयोग किया जाता है?

जैसा कि मैंने अपने ब्लॉग, truly asynchronous operations do not "run" anywhere पर समझाया है। इस विशेष मामले में (Task.Delay(1)), एसिंक्रोनस ऑपरेशन टाइमर, पर आधारित है जो कहीं भी Thread.Sleep पर अवरुद्ध थ्रेड अवरुद्ध है। अधिकांश I/O एक ही तरीके से किया जाता है। उदाहरण के लिए HttpClient.GetAsync, ओवरलैप्ड (एसिंक्रोनस) I/O, पर आधारित है जो HTTP डाउनलोड को पूरा करने के लिए कहीं भी अवरुद्ध है।


एक बार जब आप समझ कैसे await, उसके संदर्भ का उपयोग करता है मूल कोड के माध्यम से चलने के लिए आसान है:

static void Main(string[] args) 
{ 
    Console.WriteLine("Main: " + Thread.CurrentThread.ManagedThreadId); 
    MainAsync(args).Wait(); // Note: This is the same as "var task = MainAsync(args); task.Wait();" 
    Console.WriteLine("Main End: " + Thread.CurrentThread.ManagedThreadId); 

    Console.ReadKey(); 
} 

static async Task MainAsync(string[] args) 
{ 
    Console.WriteLine("Main Async: " + Thread.CurrentThread.ManagedThreadId); 
    await thisIsAsync(); // Note: This is the same as "var task = thisIsAsync(); await task;" 
} 

private static async Task thisIsAsync() 
{ 
    Console.WriteLine("thisIsAsyncStart: " + Thread.CurrentThread.ManagedThreadId); 
    await Task.Delay(1); // Note: This is the same as "var task = Task.Delay(1); await task;" 
    Console.WriteLine("thisIsAsyncEnd: " + Thread.CurrentThread.ManagedThreadId); 
} 
  1. मुख्य थ्रेड Main को क्रियान्वित करने शुरू होता है और MainAsync कहता है।
  2. मुख्य धागा MainAsync निष्पादित कर रहा है और thisIsAsync पर कॉल करता है।
  3. मुख्य धागा thisIsAsync निष्पादित कर रहा है और Task.Delay पर कॉल करता है।
  4. Task.Delay अपने काम करता है - एक टाइमर और whatnot शुरू कर - और एक अधूरी कार्य (ध्यान दें कि Task.Delay(0) एक पूर्ण कार्य है, जो व्यवहार को परिवर्तित करता वापसी होगी) देता है।
  5. मुख्य धागा thisIsAsync पर लौटता है और Task.Delay से लौटा हुआ कार्य का इंतजार करता है। चूंकि कार्य अधूरा है, यह thisIsAsync से अपूर्ण कार्य देता है।
  6. मुख्य धागा MainAsync पर लौटता है और thisIsAsync से लौटा हुआ कार्य का इंतजार करता है। चूंकि कार्य अधूरा है, यह MainAsync से अपूर्ण कार्य देता है।
  7. मुख्य धागा Main पर लौटाता है और MainAsync से वापस किए गए कार्य पर Wait पर कॉल करता है। यह मुख्य थ्रेड को MainAsync पूर्ण होने तक अवरुद्ध कर देगा।
  8. जब Task.Delay द्वारा निर्धारित टाइमर बंद हो जाता है, thisIsAsync निष्पादन जारी रहेगा। चूंकि SynchronizationContext या TaskScheduler उस await द्वारा कैप्चर किए गए हैं, इसलिए यह थ्रेड पूल थ्रेड पर निष्पादित होता है।
  9. धागा पूल धागा thisIsAsync के अंत तक पहुंचता है, जो अपना कार्य पूरा करता है।
  10. MainAsync निष्पादन जारी है। चूंकि उस await द्वारा कोई संदर्भ नहीं लिया गया है, इसलिए यह थ्रेड पूल थ्रेड पर निष्पादित होता है (वास्तव में वही धागा जो thisIsAsync चला रहा था)।
  11. धागा पूल धागा MainAsync के अंत तक पहुंचता है, जो इसके कार्य को पूरा करता है।
  12. मुख्य धागा अपने कॉल से Wait पर लौटता है और Main विधि निष्पादित करता रहता है। थ्रेड पूल थ्रेड thisIsAsync और MainAsync जारी रखने के लिए उपयोग किया जाता है और अब थ्रेड पूल पर वापस नहीं आता है।

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

+0

धन्यवाद, मैं इसे सही उत्तर में बदल दूंगा क्योंकि इसमें अधिक जानकारी है, और यह पिछले जवाब को गलत बना रहा है। – Robert

+1

बहुत बढ़िया जवाब। बहुत ज्यादा अधिमूल्यित। – gottlieb76

+0

@StephenCleary तो जब निष्पादन 'मुख्य' तक पहुंच जाता है तो यह प्रतीक्षा कर रहा है (या निष्पादन रोकता है)। क्या यह इस आईएसएसिंक में निरंतरता (मुख्य धागे में निष्पादन रोक के बाद) थ्रेड पूल में धागे द्वारा उठाया जाता है और इसे निष्पादित करता है? साथ ही, निष्पादन थ्रेड पूल (या यूआई ऐप में मुख्य धागा) से अलग थ्रेड में निरंतरता को कैसे शुरू करता है? –

1

बनाना async साथ विधि मतलब यह नहीं है कि यह देख रही है कि अपने विधि है जो अपने async विधि में await साथ कहा जाता है देरी हो रही है, यह उस विधि से बाहर निकलने के बाद किया जाता है और प्रतीक्षा कर रहे थे इंतजार कर रहा है रनटाइम thread.If एक और पैदा करेगा तरीकों समाप्त और फिर एक और thread.Try साथ कि विधि जारी रखने के लिए अपने Task.Delay(2000)Task.Delay(0) को बदलने के लिए और आपको लगता है कि यह एक नया थ्रेड नहीं बना है देखेंगे।

रनटाइम अगर इसे बनाने नहीं तो यह पैदा करेगा करने की जरूरत है, उसे गिना जाएगा - नहीं.मैं 0 एमएस के साथ अपने उदाहरण की कोशिश की और प्राप्त सभी एक ही धागा:

Main: 1 
Main Async: 1 
thisIsAsyncStart: 1 
thisIsAsyncEnd: 1 
Main End: 1 

Taken from Stephen Toub's blog:

"async" कीवर्ड

क्या "async" कीवर्ड जब करने के लिए लागू करता है एक विधी?

  1. आप संकलक कह रहे हैं कि आप का उपयोग करने के लिए "का इंतजार है सक्षम होना चाहते हैं:

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

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

क्या विधि विधि पर "async" कीवर्ड का उपयोग करना उस विधि के सभी आमंत्रण को असीमित होने के लिए उपयोग करता है?

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

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

+3

यह इंगित करने के लिए सामान्य सौजन्य है कि: [ए) आप किसी को उद्धृत कर रहे हैं, बी) आप कौन उद्धृत कर रहे हैं, और सी) उद्धरण का स्रोत] (http://blogs.msdn.com/b/pfxteam/ संग्रह/2012/04/12/async-await-faq.aspx) –

+0

मैंने 'कार्य। डेले (1)' का उपयोग करने के लिए कोड बदल दिया है और मुझे अभी भी आउटपुट में एक ही परिणाम मिला है। – Robert

+0

@ कार्यप्रणाली 'कार्य। डेले (0)' के साथ सभी धागे एक ही आईडी साझा करते हैं, लेकिन यह मेरे प्रश्न की व्याख्या नहीं करता है। क्या इस तरह के मामलों में नए धागे का उपयोग किया जाता है, और इस विशेष मामले की जरूरतों का मूल्यांकन करके असिंक वास्तव में एक नया धागा बनाता है? – Robert

0

मुझे बिल्कुल वही आश्चर्य हुआ। मेरे लिए MSDN द्वारा स्पष्टीकरण विरोधाभासी थे:

async और इंतजार कीवर्ड अतिरिक्त धागे बनाया जा करने के लिए कारण नहीं है।Async विधियों को multithreading की आवश्यकता नहीं है क्योंकि एक async विधि अपने स्वयं के धागे पर नहीं चलती है।

MSDN: Asynchronous Programming with async and await

कोई व्यंजक का इंतजार धागा जिस पर यह निष्पादित हो रहा है ब्लॉक नहीं करता। [..] जब कार्य पूरा हो जाता है, तो को इसकी निरंतरता का आह्वान करता है, और एसिंक विधि का निष्पादन फिर से शुरू होता है जहां यह छोड़ा जाता है।

await (C#-Referenz)

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

लेकिन फिर मुझे एहसास हुआ कि सबकुछ सही ढंग से लिखा गया है, कि इन कीवर्ड द्वारा किसी भी अन्य धागे का उपयोग नहीं किया गया है। यह Task कक्षा के डिजाइन द्वारा विभिन्न तंत्रों का उपयोग करने वाली तंत्र प्रदान करने के लिए है - या नहीं।

जबकि stephen-cleary beautifully explainedTask.Delay() विधि के लिए इन तंत्रों, मैं MSDN उदाहरण बढ़ाया जानने के लिए कैसे Task.Run() साथ await बर्ताव करता है:

private async void ds_StartButton_Click(object sender, EventArgs e) 
{ 
    textBox1.AppendText(DateTime.Now.ToString() + " [" + Thread.CurrentThread.ManagedThreadId + "] Started MSDN Example ..." + Environment.NewLine); 

    // Call the method that runs asynchronously. 
    string result = await WaitAsynchronouslyAsync(); 

    // Call the method that runs synchronously. 
    //string result = await WaitSynchronously(); 

    // Do other Schdaff 
    textBox1.AppendText(DateTime.Now.ToString() + " [" + Thread.CurrentThread.ManagedThreadId + "] Foobar #1 ..." + Environment.NewLine); 
    textBox1.AppendText(DateTime.Now.ToString() + " [" + Thread.CurrentThread.ManagedThreadId + "] Foobar #2 ..." + Environment.NewLine); 
    textBox1.AppendText(DateTime.Now.ToString() + " [" + Thread.CurrentThread.ManagedThreadId + "] Foobar #3 ..." + Environment.NewLine); 
    textBox1.AppendText(DateTime.Now.ToString() + " [" + Thread.CurrentThread.ManagedThreadId + "] Foobar #4 ..." + Environment.NewLine); 
    textBox1.AppendText(DateTime.Now.ToString() + " [" + Thread.CurrentThread.ManagedThreadId + "] Foobar #5 ..." + Environment.NewLine); 
    textBox1.AppendText(DateTime.Now.ToString() + " [" + Thread.CurrentThread.ManagedThreadId + "] Foobar #6 ..." + Environment.NewLine); 
    textBox1.AppendText(DateTime.Now.ToString() + " [" + Thread.CurrentThread.ManagedThreadId + "] Foobar #7 ..." + Environment.NewLine); 

    // Display the result. 
    textBox1.Text += result; 
} 

// The following method runs asynchronously. The UI thread is not 
// blocked during the delay. You can move or resize the Form1 window 
// while Task.Delay is running. 
public async Task<string> WaitAsynchronouslyAsync() 
{ 
    Console.WriteLine(DateTime.Now.ToString() + " [" + Thread.CurrentThread.ManagedThreadId + "] Entered WaitAsynchronouslyAsync()"); 
    await Task.Delay(10000); 
    Console.WriteLine(DateTime.Now.ToString() + " [" + Thread.CurrentThread.ManagedThreadId + "] Task.Delay done, starting random string generation now ..."); 

    await Task.Run(() => LongComputation()); 

    Console.WriteLine(DateTime.Now.ToString() + " [" + Thread.CurrentThread.ManagedThreadId + "] Leaving WaitAsynchronouslyAsync() ..."); 
    return DateTime.Now.ToString() + " [" + Thread.CurrentThread.ManagedThreadId + "] Finished MSDN Example." + Environment.NewLine; 
} 

// The following method runs synchronously, despite the use of async. 
// You cannot move or resize the Form1 window while Thread.Sleep 
// is running because the UI thread is blocked. 
public async Task<string> WaitSynchronously() 
{ 
    // Add a using directive for System.Threading. 
    Thread.Sleep(10000); 
    return DateTime.Now.ToString() + " [" + Thread.CurrentThread.ManagedThreadId + "] Finished MSDN Bad Ass Example." + Environment.NewLine; 
} 

private void ds_ButtonTest_Click(object sender, EventArgs e) 
{ 
    textBox1.AppendText(DateTime.Now.ToString() + " [" + Thread.CurrentThread.ManagedThreadId + "] Started Test ..." + Environment.NewLine); 
    Task<string> l_Task = WaitAsynchronouslyAsync(); 
    //WaitAsynchronouslyAsync(); 

    //textBox1.AppendText(l_Result); 
} 

private void LongComputation() 
{ 
    Console.WriteLine(DateTime.Now.ToString() + " [" + Thread.CurrentThread.ManagedThreadId + "] Generating random string ..."); 

    string l_RandomString = GetRandomString(10000000); 

    Console.WriteLine(DateTime.Now.ToString() + " [" + Thread.CurrentThread.ManagedThreadId + "] Random string generated."); 
} 

/// <summary>Get random string with specified length</summary> 
/// <param name="p_Length">Requested length of random string</param> 
/// <param name="p_NoDots">Use case of this is unknown, but assumed to be importantly needed somewhere. Defaults to true therefore. 
/// But due to huge performance implication, added this parameter to switch this off.</param> 
/// <returns>Random string</returns> 
public static string GetRandomString(int p_Length, bool p_NoDots = true) 
{ 
    StringBuilder l_StringBuilder = new StringBuilder(); 
    string l_RandomString = string.Empty; 

    while (l_StringBuilder.Length <= p_Length) 
    { 
     l_RandomString = (p_NoDots ? System.IO.Path.GetRandomFileName().Replace(".", string.Empty) : System.IO.Path.GetRandomFileName()); 
     l_StringBuilder.Append(l_RandomString); 
    } 

    l_RandomString = l_StringBuilder.ToString(0, p_Length); 
    l_StringBuilder = null; 

    return l_RandomString; 
} 

आप उत्पादन से देख सकते हैं, वहाँ कई इस्तेमाल किया धागे कर रहे हैं - नहीं

04.11.2016 12:38:06 [10] Entered WaitAsynchronouslyAsync() 
04.11.2016 12:38:17 [10] Task.Delay done, starting random string generation now ... 
04.11.2016 12:38:17 [12] Generating random string ... 
04.11.2016 12:38:21 [12] Random string generated. 
04.11.2016 12:38:21 [10] Leaving WaitAsynchronouslyAsync() ... 

यह हमेशा की तरह के रूप में यह कभी किया गया है है, बू: async/await, लेकिन Task.Run() द्वारा द्वारा मुझे यह समझने के लिए व्यक्तिगत रूप से इस स्पष्ट उदाहरण की आवश्यकता है कि क्या हो रहा है और async/await द्वारा किए गए कार्यों को अलग करने के लिए और Task द्वारा क्या किया जाता है।

+0

अच्छा उदाहरण। लेकिन ध्यान रखें कि दो प्रकार के कार्य हैं: प्रतिनिधि कार्य और वादा कार्य। केवल प्रतिनिधि कार्य वास्तव में कोड निष्पादित करते हैं (और इस प्रकार वे एक थ्रेड है जो वे चलते हैं)। ये 'स्टार्टन्यू' हैं और (कुछ हद तक) 'कार्य। रुन'। 'प्रतीक्षा' के साथ उपयोग किए जाने वाले कार्यों का विशाल बहुमत वादा कार्य है, जिसमें कोई कोड नहीं है (और कोई धागा नहीं)। ये I/O ऑपरेशंस हैं, 'टास्क। डेले', 'एसिंक टास्क' इत्यादि। तो, यह सच है कि 'टास्क.रुन' थ्रेड पूल का उपयोग करता है, लेकिन यह भी सच है कि 'टास्क.रुन' प्रतिनिधि नहीं है सबसे async कोड। स्पष्टीकरण के लिए –

+0

धन्यवाद। मैं अपने असली कार्यान्वयन में पहले उपयोग के मामलों का सामना करने की उम्मीद कर रहा हूं। – Nicolas

0

अपने प्रश्न के लिए बहुत अच्छा विवरण यहाँ है https://blogs.msdn.microsoft.com/pfxteam/2012/01/20/await-synchronizationcontext-and-console-apps/

जब अपने कंसोल अनुप्रयोग की मुख्य विधि शुरू हो जाती है, SynchronizationContext.Current अशक्त वापस आ जाएगी। इसका मतलब है कि अगर आप अपने कंसोल अनुप्रयोग में एक अतुल्यकालिक विधि आह्वान, जब तक आप कुछ खास करते हैं, अपने अतुल्यकालिक तरीकों धागा आत्मीयता नहीं होगा: "। कहीं भी" उन अतुल्यकालिक तरीकों भीतर निरंतरता चल लग सकती है

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