Linq के चयन में Async इंतजार कर रहा है


180

मुझे एक मौजूदा कार्यक्रम को संशोधित करने की आवश्यकता है और इसमें निम्नलिखित कोड शामिल हैं:

var inputs = events.Select(async ev => await ProcessEventAsync(ev))
                   .Select(t => t.Result)
                   .Where(i => i != null)
                   .ToList();

लेकिन यह मेरे लिए बहुत ही अजीब लगता है, सबसे पहले asyncऔर awaitचयन में। स्टीफन क्लीरी के इस जवाब के अनुसार मुझे उन्हें छोड़ देना चाहिए।

फिर दूसरा Selectजो परिणाम का चयन करता है। क्या इसका मतलब यह नहीं है कि कार्य बिल्कुल भी समान नहीं है और इसे सिंक्रोनाइज़ किया जाता है (कुछ भी नहीं करने के लिए बहुत अधिक प्रयास), या कार्य को एसिंक्रोनस रूप से निष्पादित किया जाएगा और जब यह किया जाता है तो बाकी क्वेरी निष्पादित होती है?

क्या मुझे स्टीफन क्लीरी के एक अन्य उत्तर के अनुसार उपरोक्त कोड लिखना चाहिए :

var tasks = await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev)));
var inputs = tasks.Where(result => result != null).ToList();

और क्या यह पूरी तरह से ऐसा ही है?

var inputs = (await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev))))
                                       .Where(result => result != null).ToList();

जबकि मैं इस परियोजना पर काम कर रहा हूँ, मैं पहले कोड के नमूने को बदलना चाहता हूँ, लेकिन मैं बहुत ज्यादा नहीं हूँ (स्पष्ट रूप से काम कर रहा)। शायद मैं कुछ भी नहीं के लिए चिंता कर रहा हूँ और सभी 3 कोड नमूने बिल्कुल एक ही काम करते हैं?

ProcessEventsAsync इस तरह दिखता है:

async Task<InputResult> ProcessEventAsync(InputEvent ev) {...}

ProceesEventAsync का रिटर्न प्रकार क्या है?
tede24

@ tede24 यह Task<InputResult>साथ InputResultएक कस्टम वर्ग जा रहा है।
अलेक्जेंडर डर्क

मेरे विचार में आपके संस्करण पढ़ने में बहुत आसान हैं। हालाँकि, आप Selectअपने कार्यों से पहले के परिणामों को भूल गए हैं Where
मैक्स

और InputResult के पास एक परिणाम संपत्ति सही है?
tede24

@ tede24 परिणाम मेरे वर्ग की नहीं बल्कि कार्य की संपत्ति है। और @ मोम का इंतजार सुनिश्चित करना चाहिए कि मुझे बिना Resultकाम की संपत्ति तक पहुँचने के परिणाम
मिलें

जवाबों:


185
var inputs = events.Select(async ev => await ProcessEventAsync(ev))
                   .Select(t => t.Result)
                   .Where(i => i != null)
                   .ToList();

लेकिन यह मेरे लिए बहुत ही अजीब लगता है, सबसे पहले एसिंक्स का उपयोग करें और चयन में प्रतीक्षा करें। स्टीफन क्लीरी के इस जवाब के अनुसार मुझे उन्हें छोड़ देना चाहिए।

कॉल को Selectमान्य है। ये दो लाइनें अनिवार्य रूप से समान हैं:

events.Select(async ev => await ProcessEventAsync(ev))
events.Select(ev => ProcessEventAsync(ev))

(इस बारे में एक मामूली अंतर है कि कैसे एक तुल्यकालिक अपवाद से फेंक दिया जाएगा ProcessEventAsync, लेकिन इस कोड के संदर्भ में यह बिल्कुल भी मायने नहीं रखता है।)

फिर दूसरा चयन करें जो परिणाम का चयन करता है। क्या इसका मतलब यह नहीं है कि कार्य बिल्कुल भी समान नहीं है और इसे सिंक्रोनाइज़ किया जाता है (कुछ भी नहीं करने के लिए बहुत अधिक प्रयास), या कार्य को एसिंक्रोनस रूप से निष्पादित किया जाएगा और जब यह किया जाता है तो बाकी क्वेरी निष्पादित होती है?

इसका मतलब है कि क्वेरी अवरुद्ध है। तो यह वास्तव में अतुल्यकालिक नहीं है।

इसे तोड़कर:

var inputs = events.Select(async ev => await ProcessEventAsync(ev))

पहले प्रत्येक घटना के लिए एक अतुल्यकालिक ऑपरेशन शुरू करेगा। फिर यह पंक्ति:

                   .Select(t => t.Result)

एक समय में एक को पूरा करने के लिए उन कार्यों की प्रतीक्षा करेगा (पहले यह पहले घटना के संचालन की प्रतीक्षा करता है, फिर अगला, फिर अगला, आदि)।

यह वह हिस्सा है जिसकी मुझे परवाह नहीं है, क्योंकि यह किसी भी अपवाद को अवरुद्ध करता है और इसमें भी लिप्त होता है AggregateException

और क्या यह पूरी तरह से ऐसा ही है?

var tasks = await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev)));
var inputs = tasks.Where(result => result != null).ToList();

var inputs = (await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev))))
                                       .Where(result => result != null).ToList();

हां, वे दो उदाहरण समकक्ष हैं। वे दोनों सभी अतुल्यकालिक संचालन शुरू करते हैं ( events.Select(...)), फिर अतुल्यकालिक रूप से किसी भी क्रम ( await Task.WhenAll(...)) में पूरा होने के लिए सभी कार्यों की प्रतीक्षा करते हैं , फिर बाकी काम ( Where...) के साथ आगे बढ़ते हैं ।

ये दोनों उदाहरण मूल कोड से अलग हैं। मूल कोड अवरुद्ध है और इसमें अपवादों को लपेटा जाएगा AggregateException


कि समाशोधन के लिए चीयर्स! इसलिए AggregateExceptionI में लिपटे अपवादों के बजाय दूसरे कोड में कई अलग-अलग अपवाद मिलेंगे?
अलेक्जेंडर डर्क

1
@AlexanderDerck: नहीं, पुराने और नए कोड दोनों में, केवल पहला अपवाद ही उठाया जाएगा। लेकिन इसके साथ Resultमें लपेटा जाएगा AggregateException
स्टीफन क्लीरी

मुझे इस कोड का उपयोग करके अपने ASP.NET MVC कंट्रोलर में गतिरोध मिल रहा है। मैंने इसे Task.Run (…) का उपयोग करके हल किया। मुझे इसके बारे में अच्छी अनुभूति नहीं है। हालाँकि, यह एक async xUnit परीक्षण में चलने के दौरान ही सही समाप्त हुआ। क्या चल रहा है?
SuperJMN

2
@SuperJMN: बदलें stuff.Select(x => x.Result);साथawait Task.WhenAll(stuff)
स्टीफन Cleary

1
@ डैनियल: वे अनिवार्य रूप से एक ही हैं। कुछ अंतर हैं जैसे कि राज्य मशीनें, संदर्भ कैप्चर करना, समकालिक अपवादों का व्यवहार। अधिक जानकारी blog.stephencleary.com/2016/12/eliding-async-await.html पर
स्टीफन

25

मौजूदा कोड काम कर रहा है, लेकिन थ्रेड को ब्लॉक कर रहा है।

.Select(async ev => await ProcessEventAsync(ev))

हर घटना के लिए एक नया कार्य बनाता है, लेकिन

.Select(t => t.Result)

प्रत्येक नए कार्य के समाप्त होने की प्रतीक्षा कर रहे धागे को अवरुद्ध करता है।

दूसरी ओर आपका कोड समान परिणाम देता है, लेकिन एसिंक्रोनस रखता है।

आपके पहले कोड पर सिर्फ एक टिप्पणी। यह रेखा

var tasks = await Task.WhenAll(events...

एकल टास्क का उत्पादन करेगा ताकि चर को एकवचन में नाम दिया जाए।

अंत में आपका अंतिम कोड समान है, लेकिन अधिक रसीला है

संदर्भ के लिए: Task.Wait / Task.WhenAll


तो पहला कोड ब्लॉक वास्तव में सिंक्रोनाइज़ किया गया है?
अलेक्जेंडर डर्क

1
हां, क्योंकि परिणाम तक पहुंचने से एक प्रतीक्षा होती है जो धागे को अवरुद्ध करती है। दूसरी ओर जब आप एक नया कार्य का निर्माण करते हैं तो आप प्रतीक्षा कर सकते हैं।
tede24

1
इस सवाल पर वापस आते हैं और tasksचर के नाम के बारे में आपकी टिप्पणी को देखते हुए , आप पूरी तरह से सही हैं। भयानक विकल्प, वे कार्य भी नहीं कर रहे हैं क्योंकि वे तुरंत इंतजार कर रहे हैं। मैं सिर्फ सवाल छोड़ दूंगा जैसा कि
अलेक्जेंडर डर्क

13

Linq में उपलब्ध मौजूदा तरीकों से यह काफी बदसूरत लग रहा है:

var tasks = items.Select(
    async item => new
    {
        Item = item,
        IsValid = await IsValid(item)
    });
var tuples = await Task.WhenAll(tasks);
var validItems = tuples
    .Where(p => p.IsValid)
    .Select(p => p.Item)
    .ToList();

उम्मीद है कि .NET के निम्नलिखित संस्करण संग्रह के कार्यों और कार्यों के संग्रह को संभालने के लिए अधिक सुरुचिपूर्ण टूलिंग के साथ आएंगे।


12

मैंने इस कोड का उपयोग किया है:

public static async Task<IEnumerable<TResult>> SelectAsync<TSource,TResult>(this IEnumerable<TSource> source, Func<TSource, Task<TResult>> method)
{
      return await Task.WhenAll(source.Select(async s => await method(s)));
}

इस तरह:

var result = await sourceEnumerable.SelectAsync(async s=>await someFunction(s,other params));

5
यह सिर्फ मौजूदा कार्यक्षमता को अधिक अस्पष्ट तरीके से इमो में लपेटता है
अलेक्जेंडर डर्क

विकल्प है var result = wait Task.WhenAll (sourceEnumerable.Select (async s => थोडा इंतजार करें), यह काम करता है, भी, लेकिन यह LINQy नहीं है
Siteite Zackwehdex

नहीं करना चाहिए Func<TSource, Task<TResult>> methodशामिल other paramsकोड के दूसरे बिट पर उल्लेख किया?
Matramos

2
अतिरिक्त पैरामीटर बाहरी हैं, उस फ़ंक्शन के आधार पर जिसे मैं निष्पादित करना चाहता हूं, वे विस्तार विधि के संदर्भ में अप्रासंगिक हैं।
साइडराइट Zackwehdex

4
यह एक सुंदर विस्तार विधि है। यह निश्चित नहीं है कि इसे "अधिक अस्पष्ट" क्यों माना गया - यह शब्दार्थ समकालिक रूप से समरूप है Select(), इसलिए एक सुरुचिपूर्ण ड्रॉप-इन है।
nullPainter

11

मैं इसे विस्तार विधि के रूप में पसंद करता हूं:

public static async Task<IEnumerable<T>> WhenAll<T>(this IEnumerable<Task<T>> tasks)
{
    return await Task.WhenAll(tasks);
}

ताकि यह विधि के साथ प्रयोग करने योग्य हो:

var inputs = await events
  .Select(async ev => await ProcessEventAsync(ev))
  .WhenAll()

1
Waitजब आप वास्तव में प्रतीक्षा नहीं कर रहे हैं तो आपको विधि को कॉल नहीं करना चाहिए । यह एक ऐसा कार्य बना रहा है जो सभी कार्यों के पूरा होने पर पूरा होता है। इसे कॉल करें WhenAll, जिस Taskविधि से यह अनुकरण करता है। यह विधि के लिए भी व्यर्थ है async। बस कॉल करें WhenAllऔर इसके साथ किया जाए।
'15:57

मेरी राय में एक बेकार आवरण का थोड़ा सा जब यह मूल विधि कहता है
अलेक्जेंडर डर्क

@ Servy उचित बिंदु, लेकिन मैं विशेष रूप से नाम विकल्पों में से किसी को पसंद नहीं करता। जब सभी बनाता है यह एक घटना की तरह लगता है जो यह काफी नहीं है।
डेरिल

3
@AlexanderDerck लाभ यह है कि आप इसे विधि जंजीर में उपयोग कर सकते हैं।
डेरिल

1
@ डेरिलल क्योंकि WhenAllएक मूल्यांकन की गई सूची (यह आलसी का मूल्यांकन नहीं किया गया है) लौटाता है, यह Task<T[]>संकेत देने के लिए रिटर्न प्रकार का उपयोग करने के लिए एक तर्क दिया जा सकता है। जब प्रतीक्षा की जाती है, तो यह अभी भी लिनक का उपयोग करने में सक्षम होगा, लेकिन यह भी संचार करता है कि यह आलसी नहीं है।
JAD
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.