जब सही ढंग से Task.Run का उपयोग करें और जब बस async-इंतजार करें


317

मैं आपको सही आर्किटेक्चर के बारे में आपकी राय पर पूछना चाहता हूं कि कब उपयोग करना है Task.Run। मैं अपने WPF .NET 4.5 एप्लीकेशन (कैलिबर्न माइक्रो फ्रेमवर्क के साथ) में laggy UI का अनुभव कर रहा हूं।

मूल रूप से मैं (बहुत सरलीकृत कोड स्निपेट) कर रहा हूं:

public class PageViewModel : IHandle<SomeMessage>
{
   ...

   public async void Handle(SomeMessage message)
   {
      ShowLoadingAnimation();

      // Makes UI very laggy, but still not dead
      await this.contentLoader.LoadContentAsync();

      HideLoadingAnimation();
   }
}

public class ContentLoader
{
    public async Task LoadContentAsync()
    {
        await DoCpuBoundWorkAsync();
        await DoIoBoundWorkAsync();
        await DoCpuBoundWorkAsync();

        // I am not really sure what all I can consider as CPU bound as slowing down the UI
        await DoSomeOtherWorkAsync();
    }
}

मेरे द्वारा पढ़े / देखे गए लेखों / वीडियो से, मुझे पता है कि await asyncजरूरी नहीं कि एक पृष्ठभूमि पर चल रहा हो और पृष्ठभूमि में काम शुरू करने के लिए आपको इसे प्रतीक्षा के साथ लपेटने की आवश्यकता हो Task.Run(async () => ... )। का उपयोग करते हुए async awaitयूआई ब्लॉक नहीं करता है, लेकिन अभी भी यह यूआई धागा पर चल रहा है, तो यह यह laggy कर रही है।

टास्क लगाने के लिए सबसे अच्छी जगह कहाँ है।

क्या मुझे बस चाहिए?

  1. बाहरी कॉल को लपेटें क्योंकि यह .NET के लिए थ्रेडिंग का कम काम है

  2. , या मुझे केवल सीपीयू-बाउंड विधियों को आंतरिक Task.Runरूप से चालू करना चाहिए क्योंकि यह अन्य स्थानों के लिए पुन: प्रयोज्य बनाता है? मुझे यकीन नहीं है कि अगर कोर में गहरी पृष्ठभूमि के धागे पर काम शुरू करना एक अच्छा विचार है।

विज्ञापन (1), पहला समाधान इस तरह होगा:

public async void Handle(SomeMessage message)
{
    ShowLoadingAnimation();
    await Task.Run(async () => await this.contentLoader.LoadContentAsync());
    HideLoadingAnimation();
}

// Other methods do not use Task.Run as everything regardless
// if I/O or CPU bound would now run in the background.

विज्ञापन (2), दूसरा समाधान इस तरह होगा:

public async Task DoCpuBoundWorkAsync()
{
    await Task.Run(() => {
        // Do lot of work here
    });
}

public async Task DoSomeOtherWorkAsync(
{
    // I am not sure how to handle this methods -
    // probably need to test one by one, if it is slowing down UI
}

BTW, लाइन में (1) await Task.Run(async () => await this.contentLoader.LoadContentAsync());बस होना चाहिए await Task.Run( () => this.contentLoader.LoadContentAsync() );। AFAIK आप एक दूसरे awaitऔर asyncअंदर जोड़कर कुछ हासिल नहीं करते Task.Run। और जब से आप पैरामीटर पारित नहीं कर रहे हैं, यह थोड़ा और अधिक सरल करता है await Task.Run( this.contentLoader.LoadContentAsync );
टूलमेकरसेव

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

जवाबों:


365

मेरे ब्लॉग पर एकत्रित UI थ्रेड पर कार्य करने के दिशानिर्देशों पर ध्यान दें :

  • एक समय में 50ms से अधिक के लिए UI थ्रेड को ब्लॉक न करें।
  • आप प्रति सेकंड UI थ्रेड पर ~ 100 निरंतरता अनुसूची कर सकते हैं; 1000 बहुत ज्यादा है।

ऐसी दो तकनीकें हैं जिनका आपको उपयोग करना चाहिए:

1) का उपयोग करें ConfigureAwait(false)जब आप कर सकते हैं।

जैसे, की await MyAsync().ConfigureAwait(false);जगह await MyAsync();

ConfigureAwait(false)बताता है awaitकि आपको वर्तमान संदर्भ (इस मामले में, "वर्तमान संदर्भ पर" का अर्थ "UI थ्रेड पर") को फिर से शुरू करने की आवश्यकता नहीं है। हालाँकि, उस asyncपद्धति के बाकी ( ConfigureAwait) के बाद , आप ऐसा कुछ भी नहीं कर सकते हैं जो आप वर्तमान संदर्भ में मानते हैं (उदाहरण के लिए, यूआई तत्वों को अपडेट करें)।

अधिक जानकारी के लिए, एसिंक्रोनस प्रोग्रामिंग में मेरा एमएसडीएन लेख सर्वश्रेष्ठ अभ्यास देखें ।

2) Task.Runसीपीयू-बाउंड विधियों को कॉल करने के लिए उपयोग करें।

आप का उपयोग करना चाहिए Task.Run, लेकिन किसी भी कोड के भीतर आप पुन: प्रयोज्य होना चाहते हैं (यानी, पुस्तकालय कोड)। तो आप विधि Task.Runको कॉल करने के लिए उपयोग करते हैं , विधि के कार्यान्वयन के भाग के रूप में नहीं ।

तो विशुद्ध रूप से सीपीयू-बाउंड काम इस तरह दिखेगा:

// Documentation: This method is CPU-bound.
void DoWork();

जिसे आप उपयोग करके कहेंगे Task.Run:

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

सीपीयू-बाउंड और आई / ओ-बाउंड का मिश्रण करने वाले तरीकों में Asyncप्रलेखन के साथ एक हस्ताक्षर होना चाहिए जो उनके सीपीयू-बाउंड प्रकृति को इंगित करता है:

// Documentation: This method is CPU-bound.
Task DoWorkAsync();

जिसे आप उपयोग करके भी कॉल करेंगे Task.Run(क्योंकि यह आंशिक रूप से CPU-बाउंड है):

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

4
आपके उपवास के लिए धन्यवाद! मैं आपके द्वारा पोस्ट किए गए लिंक को जानता हूं और उन वीडियो को देखा है जो आपके ब्लॉग में संदर्भित हैं। वास्तव में यही कारण है कि मैंने यह प्रश्न पोस्ट किया है - वीडियो में कहा गया है (आपकी प्रतिक्रिया के अनुसार) आपको कोर कोड में Task.Run का उपयोग नहीं करना चाहिए। लेकिन मेरी समस्या यह है, कि मुझे हर बार इस तरह की विधि को लपेटने की आवश्यकता होती है, जब मैं इसका उपयोग जिम्मेदारियों को धीमा नहीं करने के लिए करता हूं (कृपया ध्यान दें कि मेरा सभी कोड async है और ब्लॉक नहीं हो रहा है, लेकिन थ्रेड के बिना। यह सिर्फ अंतराल है)। मैं इस उलझन में हूं कि क्या सीपीयू बाउंड मेथड (कई टास्क.ऑन कॉल) को लपेटना बेहतर है या पूरी तरह से एक टास्क में लपेटना है।
लुकास के

12
आपके सभी पुस्तकालय विधियों का उपयोग किया जाना चाहिए ConfigureAwait(false)। यदि आप पहले ऐसा करते हैं, तो आप पा सकते हैं कि Task.Runयह पूरी तरह से अनावश्यक है। यदि आप करते हैं अभी भी जरूरत है Task.Run, तो यह इस मामले कि आप इसे एक या कई बार फोन में क्रम के लिए बहुत कुछ फर्क नहीं करता है, तो बस क्या आपके कोड के लिए सबसे स्वाभाविक है है।
स्टीफन क्लीयर

2
मुझे समझ नहीं आ रहा है कि पहली तकनीक उसकी कैसे मदद करेगी। यहां तक ​​कि अगर आप ConfigureAwait(false)अपने सीपीयू-बाउंड विधि पर उपयोग करते हैं, तो यह अभी भी यूआई धागा है जो सीपीयू-बाउंड विधि करने जा रहा है, और टीपी थ्रेड पर होने के बाद ही सब कुछ हो सकता है। या मैंने कुछ गलत समझा?
डेरियस

4
@ user4205580: नहीं, Task.Runअतुल्यकालिक हस्ताक्षर समझता है, इसलिए यह पूरा होने तक पूरा नहीं होगा DoWorkAsync। अतिरिक्त async/ awaitअनावश्यक है। मैं शिष्टाचार परTask.Run अपनी ब्लॉग श्रृंखला में "क्यों" की अधिक व्याख्या करता हूं ।
स्टीफन क्लीयर

3
@ user4205580: नहीं, "कोर" async विधियों का विशाल बहुमत आंतरिक रूप से इसका उपयोग नहीं करता है। एक "कोर" async विधि को लागू करने का सामान्य तरीका TaskCompletionSource<T>अपने शॉर्टहैंड नोटेशन जैसे कि एक का उपयोग करना या करना है FromAsync। मेरे पास एक ब्लॉग पोस्ट है जो अधिक विस्तार में जाती है कि async विधियों को थ्रेड्स की आवश्यकता क्यों नहीं है
स्टीफन क्लीयर

12

आपके ContentLoader के साथ एक समस्या यह है कि आंतरिक रूप से यह क्रमिक रूप से संचालित होता है। एक बेहतर पैटर्न काम को समानांतर करना है और फिर अंत में sychronize है, इसलिए हम प्राप्त करते हैं

public class PageViewModel : IHandle<SomeMessage>
{
   ...

   public async void Handle(SomeMessage message)
   {
      ShowLoadingAnimation();

      // makes UI very laggy, but still not dead
      await this.contentLoader.LoadContentAsync(); 

      HideLoadingAnimation();   
   }
}

public class ContentLoader 
{
    public async Task LoadContentAsync()
    {
        var tasks = new List<Task>();
        tasks.Add(DoCpuBoundWorkAsync());
        tasks.Add(DoIoBoundWorkAsync());
        tasks.Add(DoCpuBoundWorkAsync());
        tasks.Add(DoSomeOtherWorkAsync());

        await Task.WhenAll(tasks).ConfigureAwait(false);
    }
}

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.