क्या आपको Async बनाने के लिए Task.Run को एक विधि में रखना है?


304

मैं सबसे आसान रूप में async प्रतीक्षा का समझने की कोशिश कर रहा हूँ। मैं एक बहुत ही सरल विधि बनाना चाहता हूं जो इस उदाहरण के लिए दो नंबर जोड़ता है, दी गई है, यह बिल्कुल भी प्रसंस्करण समय नहीं है, यह सिर्फ एक उदाहरण तैयार करने की बात है।

उदाहरण 1

private async Task DoWork1Async()
{
    int result = 1 + 2;
}

उदाहरण 2

private async Task DoWork2Async()
{
    Task.Run( () =>
    {
        int result = 1 + 2;
    });
}

अगर मुझे इंतजार है DoWork1Async()तो कोड सिंक्रोनाइज़ या एसिंक्रोनस रूप से चलेगा?

क्या मुझे Task.Runविधि को प्रतीक्षा योग्य और अतुल्यकालिक बनाने के लिए सिंक कोड को लपेटने की आवश्यकता है ताकि UI थ्रेड को ब्लॉक न किया जा सके?

मैं यह पता लगाने की कोशिश कर रहा हूं कि क्या मेरा तरीका एक Taskया रिटर्न Task<T>है, मुझे Task.Runइसे अतुल्यकालिक बनाने के लिए कोड को लपेटने की आवश्यकता है ।

बेवकूफ सवाल मुझे यकीन है, लेकिन मैं शुद्ध, जहां लोगों को कोड के भीतर कुछ भी नहीं async है और में एक लपेटा नहीं कि इंतजार कर रहे हैं पर उदाहरण देखें Task.Runया StartNew


30
क्या आपका पहला स्निपेट आपको कोई चेतावनी नहीं दे रहा है?
svick

जवाबों:


586

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

यह "एसिंक्रोनस" शब्द से बहुत अलग है, जैसा कि (मिस) MSDN प्रलेखन द्वारा वर्षों से उपयोग किया जाता है, जिसका अर्थ है "एक पृष्ठभूमि धागे पर निष्पादित"।

फ़्यूचर को इस मुद्दे को भ्रमित करने के लिए, async"प्रतीक्षा योग्य" से बहुत अलग है; कुछ asyncविधियाँ हैं जिनकी वापसी प्रकार प्रतीक्षा योग्य नहीं हैं, और कई विधियाँ प्रतीक्षा योग्य प्रकार हैं जो नहीं हैं async

वे क्या नहीं कर रहे हैं के बारे में पर्याप्त ; यहाँ वे क्या हैं :

  • asyncकीवर्ड एक अतुल्यकालिक विधि की अनुमति देता है (यह है कि, यह अनुमति देता है awaitभाव)। asyncविधियां वापस आ सकती हैं Task, Task<T>या (यदि आपको चाहिए) void
  • किसी भी प्रकार जो एक निश्चित पैटर्न का अनुसरण करता है, वह प्रतीक्षा योग्य हो सकता है। सबसे आम प्रतीक्षित प्रकार हैं Taskऔर Task<T>

इसलिए, यदि हम आपके प्रश्न को "मैं एक पृष्ठभूमि थ्रेड पर ऑपरेशन कैसे चला सकता हूं , जो इस तरह से प्रतीक्षा योग्य है", तो इसका उपयोग करना है Task.Run:

private Task<int> DoWorkAsync() // No async because the method does not need await
{
  return Task.Run(() =>
  {
    return 1 + 2;
  });
}

(लेकिन यह पैटर्न एक खराब दृष्टिकोण है; नीचे देखें)।

लेकिन अगर आपका सवाल है "मैं एक asyncविधि कैसे बनाऊं जो अवरोधक के बजाय इसके कॉलर को वापस प्राप्त कर सकती है", तो इसका उत्तर विधि asyncऔर awaitउसके "उपज" बिंदुओं के लिए उपयोग करना है:

private async Task<int> GetWebPageHtmlSizeAsync()
{
  var client = new HttpClient();
  var html = await client.GetAsync("http://www.example.com/");
  return html.Length;
}

तो, चीजों का मूल पैटर्न asyncकोड का उसके awaitभावों में "प्रतीक्षा-योग्यताओं" पर निर्भर होना है । ये "वेटिटेबल्स" अन्य asyncविधियां हो सकती हैं या नियमित रूप से वेटीबल्स लौटा सकती हैं। लौटने नियमित तरीकों Task/ Task<T> कर सकते हैं का उपयोग Task.Runएक पृष्ठभूमि धागे पर कोड निष्पादित करने के लिए, या (सामान्यतः) वे उपयोग कर सकते हैं TaskCompletionSource<T>या अपने शॉर्टकट (में से एक TaskFactory.FromAsync, Task.FromResult, आदि)। मैं एक पूरी विधि को लपेटने की सलाह नहीं देता Task.Run; सिंक्रोनस मेथड्स में सिंक्रोनस सिग्नेचर होना चाहिए, और इसे उपभोक्ता तक छोड़ दिया जाना चाहिए, चाहे वह इसमें लिपटे हों Task.Run:

private int DoWork()
{
  return 1 + 2;
}

private void MoreSynchronousProcessing()
{
  // Execute it directly (synchronously), since we are also a synchronous method.
  var result = DoWork();
  ...
}

private async Task DoVariousThingsFromTheUIThreadAsync()
{
  // I have a bunch of async work to do, and I am executed on the UI thread.
  var result = await Task.Run(() => DoWork());
  ...
}

मैं एक है async/ awaitपरिचय अपने ब्लॉग पर; अंत में कुछ अच्छे फॉलोअप संसाधन हैं। MSDN डॉक्स asyncअसामान्य रूप से अच्छे हैं, भी।


8
@sgnsajgon: हाँ। asyncतरीकों लौटना चाहिए Task, Task<T>या voidTaskऔर Task<T>प्रतीक्षित हैं; voidनहीं है।
स्टीफन क्लीयर

3
वास्तव में, एक async voidविधि हस्ताक्षर संकलित करेगा, यह सिर्फ एक बहुत ही भयानक विचार है क्योंकि आप अपने पॉइंटर को अपने async कार्य के लिए ढीला करते हैं
IEATBagels

4
@TopinFrassi: हाँ, वे संकलन करेंगे, लेकिन प्रतीक्षा voidनहीं कर रहे हैं।
स्टीफन क्लीयर

4
@ohadinho: नहीं, मैं ब्लॉग पोस्ट में जिस बारे में बात कर रहा हूं वह तब है जब पूरी विधि सिर्फ एक कॉल है Task.Run(जैसे DoWorkAsyncकि इस उत्तर में)। UI संदर्भ से किसी विधि Task.Runको कॉल करने के लिए उपयोग करना उचित है (जैसे DoVariousThingsFromTheUIThreadAsync)।
स्टीफन क्लीयर

2
हाँ बिल्कुल। यह विधि Task.Runको लागू करने के लिए उपयोग करने के लिए वैध है , लेकिन यदि Task.Runविधि के कोड के लगभग (या लगभग सभी) है, तो यह एक विरोधी पैटर्न है - बस उस पद्धति को समकालिक रखें और Task.Runएक स्तर ऊपर ले जाएं ।
स्टीफन क्लीयर

22

एक सबसे महत्वपूर्ण बात यह है कि जब async के साथ एक विधि को सजाने के लिए याद रखें कि कम से कम विधि के अंदर एक प्रतीक्षित ऑपरेटर है। आपके उदाहरण में, मैं नीचे इसका अनुवाद करूँगा जैसा कि टास्क कॉमप्लेक्शन स्रोत का उपयोग करके नीचे दिखाया गया है ।

private Task<int> DoWorkAsync()
{
    //create a task completion source
    //the type of the result value must be the same
    //as the type in the returning Task
    TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
    Task.Run(() =>
    {
        int result = 1 + 2;
        //set the result to TaskCompletionSource
        tcs.SetResult(result);
    });
    //return the Task
    return tcs.Task;
}

private async void DoWork()
{
    int result = await DoWorkAsync();
}

26
टास्क.नल () (और परिणाम वापस करने के लिए अपने शरीर को बदलकर) द्वारा लौटाए गए कार्य के बदले टास्क कॉमप्लेक्शन स्रोत का उपयोग क्यों करते हैं?
व्यंग्यात्मक

4
बस एक साइड नोट। एक विधि जिसमें "async शून्य" हस्ताक्षर है, आमतौर पर बुरा व्यवहार है और इसे बुरा कोड माना जाता है क्योंकि यह UI आसानी से गतिरोध पैदा कर सकता है। मुख्य अपवाद अतुल्यकालिक घटना संचालकों है।
जज़्ज़ेरोकी

12

जब आप एक विधि को चलाने के लिए Task.Run का उपयोग करते हैं, तो टास्क को उस विधि को चलाने के लिए थ्रेडपूल से एक धागा मिलता है। यूआई थ्रेड के दृष्टिकोण से, यह "एसिंक्रोनस" है क्योंकि यह यूआई थ्रेड को ब्लॉक नहीं करता है। यह डेस्कटॉप एप्लिकेशन के लिए ठीक है क्योंकि आपको आमतौर पर उपयोगकर्ता इंटरैक्शन का ध्यान रखने के लिए कई थ्रेड्स की आवश्यकता नहीं होती है।

हालाँकि, वेब एप्लिकेशन के लिए प्रत्येक अनुरोध थ्रेड-पूल थ्रेड द्वारा सेवित है और इस प्रकार ऐसे थ्रेड्स को सहेज कर सक्रिय अनुरोधों की संख्या बढ़ाई जा सकती है। अक्सर async कार्रवाई अनुकरण करने के लिए थ्रेडपूल थ्रेड्स का उपयोग करना वेब अनुप्रयोगों के लिए स्केलेबल नहीं है।

True Async में I / O संचालन के लिए एक थ्रेड का उपयोग करना आवश्यक नहीं है, जैसे फ़ाइल / DB एक्सेस आदि। आप यह समझने के लिए पढ़ सकते हैं कि I / O ऑपरेशन को थ्रेड्स की आवश्यकता क्यों नहीं है। http://blog.stephencleary.com/2013/11/there-is-no-thread.html

आपके सरल उदाहरण में, यह एक शुद्ध सीपीयू-बाउंड गणना है, इसलिए टास्क का उपयोग करना।


इसलिए अगर मुझे एक वेब एपीआई नियंत्रक के भीतर एक तुल्यकालिक बाहरी एपीआई का उपभोग करना है, तो मुझे टास्क.रून () में सिंक्रोनस कॉल को लपेटना नहीं चाहिए? जैसा कि आपने कहा, ऐसा करने से प्रारंभिक अनुरोध थ्रेड अनब्लॉक रहेगा, लेकिन यह बाहरी एपीआई को कॉल करने के लिए दूसरे पूल थ्रेड का उपयोग कर रहा है। वास्तव में मुझे लगता है कि यह अभी भी एक अच्छा विचार है क्योंकि इस तरह से सिद्धांत रूप में यह कई अनुरोधों को संसाधित करने के लिए दो पूल थ्रेड्स का उपयोग कर सकता है जैसे कि एक धागा कई आने वाले अनुरोधों को संसाधित कर सकता है और एक अन्य इन सभी अनुरोधों के लिए बाहरी एपीआई को कॉल कर सकता है?
stt106

मैं मानता हूं। मैं यह नहीं कह रहा हूं कि आप टास्क के भीतर सभी सिंक्रोनस कॉल को लपेटें नहीं। मैं केवल संभावित मुद्दे की ओर इशारा कर रहा हूं।
झेंग यु

1
@ stt106 I should NOT wrap the synchronous call in Task.Run()यह सही है। यदि आप करते हैं, तो आप केवल थ्रेड स्विच कर रहे होंगे। यानी आप प्रारंभिक अनुरोध थ्रेड को अनब्लॉक कर रहे हैं, लेकिन आप थ्रेडपूल से एक और धागा ले रहे हैं जिसका उपयोग किसी अन्य अनुरोध को संसाधित करने के लिए किया जा सकता है। एकमात्र परिणाम एक संदर्भ स्विच ओवरहेड है जब कॉल बिल्कुल शून्य लाभ के लिए पूरा हो गया है
Saeb Amini
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.