LINQ का उपयोग करते हुए अतुल्यकालिक रूप से कार्यों की सूची का इंतजार कैसे करें?


87

मेरे पास उन कार्यों की एक सूची है जो मैंने इस तरह बनाई हैं:

public async Task<IList<Foo>> GetFoosAndDoSomethingAsync()
{
    var foos = await GetFoosAsync();

    var tasks = foos.Select(async foo => await DoSomethingAsync(foo)).ToList();

    ...
}

उपयोग करके .ToList(), कार्य सभी शुरू होने चाहिए। अब मैं उनके पूरा होने का इंतजार करना चाहता हूं और नतीजे लौटाऊंगा।

यह उपरोक्त ...ब्लॉक में काम करता है :

var list = new List<Foo>();
foreach (var task in tasks)
    list.Add(await task);
return list;

यह वही करता है जो मैं चाहता हूं, लेकिन यह अनाड़ी लगता है। मैं बहुत कुछ इस तरह सरल लिखूंगा:

return tasks.Select(async task => await task).ToList();

... लेकिन यह संकलन नहीं है। मैं क्या खो रहा हूँ? या सिर्फ इस तरह से चीजों को व्यक्त करना संभव नहीं है?


क्या आपको DoSomethingAsync(foo)प्रत्येक फू के लिए क्रमिक रूप से प्रक्रिया करने की आवश्यकता है , या क्या यह Parallel.ForEach <Foo> के लिए एक उम्मीदवार है ?
mdisibio

1
@mdisibio - Parallel.ForEachब्लॉक कर रहा है। यहां पैटर्न जॉन स्कैट्स के एसिंक्रोनस सी # वीडियो से प्लुरलसाइट पर आता है । यह बिना अवरोध के समानांतर चलता है।
मैट जॉनसन-पिंट

@ मदिसिबियो - नोप। वे समानांतर में चलते हैं। यह कोशिश करो । (इसके अतिरिक्त, ऐसा लगता है कि मुझे ज़रूरत नहीं है .ToList()अगर मैं बस उपयोग करने जा रहा हूं WhenAll।)
मैट जॉनसन-पिंट

मुद्दा लेना। कैसे DoSomethingAsyncलिखा जाता है, इसके आधार पर , सूची समानांतर में निष्पादित या नहीं हो सकती है। मैं एक परीक्षण विधि लिखने में सक्षम था जो एक ऐसा संस्करण था और ऐसा नहीं था, लेकिन किसी भी मामले में व्यवहार को विधि द्वारा ही निर्धारित किया जाता है, न कि कार्य को बनाने वाले प्रतिनिधि को। मिक्स-अप के लिए क्षमा करें। हालांकि, अगर DoSomethingAsycरिटर्न मिलता है Task<Foo>, तो awaitप्रतिनिधि में बिल्कुल जरूरी नहीं है ... मुझे लगता है कि मुख्य बिंदु मैं बनाने की कोशिश करने जा रहा था।
mdisibio

जवाबों:


136

LINQ asyncकोड के साथ पूरी तरह से काम नहीं करता है , लेकिन आप ऐसा कर सकते हैं:

var tasks = foos.Select(DoSomethingAsync).ToList();
await Task.WhenAll(tasks);

यदि आपके कार्य सभी एक ही प्रकार के मूल्य पर लौटते हैं, तो आप ऐसा भी कर सकते हैं:

var results = await Task.WhenAll(tasks);

जो काफी अच्छा है। WhenAllएक सरणी देता है, इसलिए मुझे विश्वास है कि आपकी विधि सीधे परिणाम लौटा सकती है:

return await Task.WhenAll(tasks);

11
बस का कहना चाहते थे कि इस भी कर सकते हैं के साथ कामvar tasks = foos.Select(foo => DoSomethingAsync(foo)).ToList();
mdisibio

1
या यहां तक ​​किvar tasks = foos.Select(DoSomethingAsync).ToList();
टॉड मेनियर

3
इसके पीछे क्या कारण है कि Linq does not async कोड के साथ पूरी तरह से काम करता है?
एहसान सज्जाद

2
@EhsanSajjad: क्योंकि LINQ ऑब्जेक्ट्स में सिंक्रोनस इन-मेमोरी ऑब्जेक्ट्स पर काम करता है। कुछ सीमित चीजें काम करती हैं, जैसे Select। लेकिन सबसे ज्यादा पसंद नहीं है Where
स्टीफन क्लीयर

4
@ ईशानसजद: यदि ऑपरेशन I / O- आधारित है, तो आप asyncथ्रेड्स को कम करने के लिए उपयोग कर सकते हैं ; यदि यह सीपीयू-बाउंड है और पहले से ही एक पृष्ठभूमि थ्रेड पर है, तो asyncकोई लाभ नहीं देगा।
स्टीफन क्लीयर

9

स्टीफन के उत्तर पर विस्तार करने के लिए, मैंने LINQ की धाराप्रवाह शैली को बनाए रखने के लिए निम्नलिखित विस्तार विधि बनाई है । आप तब कर सकते हैं

await someTasks.WhenAll()

namespace System.Linq
{
    public static class IEnumerableExtensions
    {
        public static Task<T[]> WhenAll<T>(this IEnumerable<Task<T>> source)
        {
            return Task.WhenAll(source);
        }
    }
}

10
व्यक्तिगत रूप से, मैं आपके एक्सटेंशन का नाम ToArrayAsync
बताऊंगा

4

Task.WhenAll के साथ एक मुद्दा यह है कि यह एक समानता पैदा करेगा। अधिकांश मामलों में यह और भी बेहतर हो सकता है, लेकिन कभी-कभी आप इससे बचना चाहते हैं। उदाहरण के लिए, DB से बैचों में डेटा पढ़ना और कुछ दूरस्थ वेब सेवा को डेटा भेजना। आप सभी बैचों को मेमोरी में लोड नहीं करना चाहते हैं, लेकिन पिछले बैच को संसाधित करने के बाद DB को हिट करें। तो, आपको एसिंक्रोनसिटी को तोड़ना होगा। यहाँ एक उदाहरण है:

var events = Enumerable.Range(0, totalCount/ batchSize)
   .Select(x => x*batchSize)
   .Select(x => dbRepository.GetEventsBatch(x, batchSize).GetAwaiter().GetResult())
   .SelectMany(x => x);
foreach (var carEvent in events)
{
}

नोट .GetAwaiter ()। GetResult () इसे सिंक्रोनाइज़ करने के लिए परिवर्तित करना। डीबी आलसी रूप से केवल एक बार मारा जाएगा जब घटनाओं का बैचसाइज हो गया हो।


1

उपयोग Task.WaitAllया Task.WhenAllजो भी approriate है।


1
वह भी काम नहीं करता है। Task.WaitAllअवरुद्ध है, प्रतीक्षा योग्य नहीं है, और साथ काम नहीं करेगा Task<T>
मैट जॉनसन-पिंट

@MattJohnson WhenAll?
LB

हां। बस! मुझेमूक होने का अनुभव हो रहा है। धन्यवाद!
मैट जॉनसन-पिंट

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