मैं ForEach के साथ Async का उपयोग कैसे कर सकता हूं?


123

क्या ForEach का उपयोग करते समय Async का उपयोग करना संभव है? नीचे मैं कोड की कोशिश कर रहा हूं:

using (DataContext db = new DataLayer.DataContext())
{
    db.Groups.ToList().ForEach(i => async {
        await GetAdminsFromGroup(i.Gid);
    });
}

मुझे त्रुटि मिल रही है:

वर्तमान संदर्भ में 'Async' नाम मौजूद नहीं है

उपयोग करने का विवरण जिस विधि में संलग्न है वह async पर सेट है।

जवाबों:


180

List<T>.ForEachविशेष रूप से अच्छी तरह से नहीं खेलता है async(न ही LINQ-to-Objects, एक ही कारणों से)।

इस स्थिति में, मैं प्रत्येक तत्व को एक एसिंक्रोनस ऑपरेशन में प्रोजेक्ट करने की सलाह देता हूं , और आप तब (एसिंक्रोनस रूप से) उन सभी को पूरा करने के लिए इंतजार कर सकते हैं।

using (DataContext db = new DataLayer.DataContext())
{
    var tasks = db.Groups.ToList().Select(i => GetAdminsFromGroupAsync(i.Gid));
    var results = await Task.WhenAll(tasks);
}

एक asyncप्रतिनिधि को इस दृष्टिकोण का लाभ इस ForEachप्रकार है:

  1. त्रुटि हैंडलिंग अधिक उचित है। से अपवादों को async voidनहीं पकड़ा जा सकता है catch; यह दृष्टिकोण await Task.WhenAllप्राकृतिक अपवाद से निपटने के लिए, लाइन में अपवादों का प्रचार करेगा ।
  2. आप जानते हैं कि इस पद्धति के अंत में कार्य पूर्ण हैं, क्योंकि यह ए await Task.WhenAll। यदि आप उपयोग करते हैं async void, तो आप आसानी से नहीं बता सकते कि ऑपरेशन कब पूरा हुआ है।
  3. इस दृष्टिकोण में परिणाम प्राप्त करने के लिए एक प्राकृतिक वाक्यविन्यास है। GetAdminsFromGroupAsyncऐसा लगता है कि यह एक ऐसा ऑपरेशन है जो एक परिणाम (प्रवेश) पैदा करता है, और ऐसा कोड अधिक स्वाभाविक है यदि ऐसे ऑपरेशन साइड इफेक्ट के रूप में मान सेट करने के बजाय अपने परिणाम वापस कर सकते हैं

5
ऐसा नहीं है कि यह कुछ भी बदलता है, लेकिन List.ForEach()LINQ का हिस्सा नहीं है।
स्विक

शानदार सुझाव @StephenCleary और आपके द्वारा दिए गए सभी उत्तरों के लिए धन्यवाद async। वे बहुत मददगार रहे हैं!
जस्टिन हेलगर्सन

4
@StewartAnderson: कार्य समवर्ती रूप से निष्पादित होंगे। धारावाहिक निष्पादन के लिए कोई विस्तार नहीं है; बस अपने पाश शरीर में एक foreachसाथ करते हैं await
स्टीफन क्लीयर

1
@are: ForEachकेवल एक समकालिक प्रतिनिधि प्रकार लेता है, और अतुल्यकालिक प्रतिनिधि प्रकार लेने में कोई अधिभार नहीं होता है। तो संक्षिप्त उत्तर है "किसी ने अतुल्यकालिक नहीं लिखा ForEach"। लंबा जवाब यह है कि आपको कुछ शब्दार्थ ग्रहण करने होंगे; उदाहरण के लिए, क्या वस्तुओं को एक बार (जैसे foreach), या एक साथ (जैसे Select) संसाधित किया जाना चाहिए ? यदि एक समय पर, अतुल्यकालिक धाराएं एक बेहतर समाधान नहीं होंगी? यदि एक साथ, परिणाम मूल आइटम क्रम में या पूरा होने के क्रम में होना चाहिए? क्या यह पहली असफलता पर विफल होना चाहिए या सभी पूरा होने तक इंतजार करना चाहिए? आदि
स्टीफन क्लीयर

2
@ रॉजरवॉल्फ: हाँ; का उपयोग SemaphoreSlimथ्रोटल अतुल्यकालिक कार्य करने के लिए।
स्टीफन क्ली

61

यह थोड़ा विस्तार विधि आपको अपवाद-सुरक्षित async पुनरावृत्ति देना चाहिए:

public static async Task ForEachAsync<T>(this List<T> list, Func<T, Task> func)
{
    foreach (var value in list)
    {
        await func(value);
    }
}

हम से लैम्ब्डा की वापसी प्रकार बदल रहे हैं के बाद से voidकरने के लिए Task, अपवाद सही ढंग से प्रचार करेंगे। यह आपको व्यवहार में कुछ इस तरह लिखने की अनुमति देगा:

await db.Groups.ToList().ForEachAsync(async i => {
    await GetAdminsFromGroup(i.Gid);
});

मेरा मानना ​​है कि asyncइससे पहले होना चाहिएi =>
टॉड

ForEachAsyn () की प्रतीक्षा करने के बजाय, कोई व्यक्ति Wait () भी कह सकता है।
जोनास

लैंबडा को यहां इंतजार करने की जरूरत नहीं है।
हज़िक

मैं
टोडन

ForEachAsyncअनिवार्य रूप से एक पुस्तकालय विधि, इसलिए का इंतजार शायद के साथ कॉन्फ़िगर किया जाना चाहिए है ConfigureAwait(false)
थियोडोर ज़ूलियास

9

इसका सरल उत्तर यह है कि आप foreachकिस ForEach()विधि के बजाय कीवर्ड का उपयोग कर सकते हैं List()

using (DataContext db = new DataLayer.DataContext())
{
    foreach(var i in db.Groups)
    {
        await GetAdminsFromGroup(i.Gid);
    }
}

आप एक प्रतिभाशाली व्यक्ति हैं
विकी_नोरिल्स

8

यहाँ अनुक्रमिक प्रसंस्करण के साथ उपरोक्त async foreach वेरिएंट का वास्तविक कार्य संस्करण है:

public static async Task ForEachAsync<T>(this List<T> enumerable, Action<T> action)
{
    foreach (var item in enumerable)
        await Task.Run(() => { action(item); }).ConfigureAwait(false);
}

यहाँ कार्यान्वयन है:

public async void SequentialAsync()
{
    var list = new List<Action>();

    Action action1 = () => {
        //do stuff 1
    };

    Action action2 = () => {
        //do stuff 2
    };

    list.Add(action1);
    list.Add(action2);

    await list.ForEachAsync();
}

मुख्य अंतर क्या है? .ConfigureAwait(false);जो प्रत्येक कार्य के async अनुक्रमिक प्रसंस्करण के दौरान मुख्य सूत्र का संदर्भ रखता है।


6

से शुरू करते हुए C# 8.0, आप असिंक्रोनस रूप से धाराओं को बना और उपभोग कर सकते हैं।

    private async void button1_Click(object sender, EventArgs e)
    {
        IAsyncEnumerable<int> enumerable = GenerateSequence();

        await foreach (var i in enumerable)
        {
            Debug.WriteLine(i);
        }
    }

    public static async IAsyncEnumerable<int> GenerateSequence()
    {
        for (int i = 0; i < 20; i++)
        {
            await Task.Delay(100);
            yield return i;
        }
    }

अधिक


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

3

इस विस्तार विधि को जोड़ें

public static class ForEachAsyncExtension
{
    public static Task ForEachAsync<T>(this IEnumerable<T> source, int dop, Func<T, Task> body)
    {
        return Task.WhenAll(from partition in Partitioner.Create(source).GetPartitions(dop) 
            select Task.Run(async delegate
            {
                using (partition)
                    while (partition.MoveNext())
                        await body(partition.Current).ConfigureAwait(false);
            }));
    }
}

और फिर इस तरह का उपयोग करें:

Task.Run(async () =>
{
    var s3 = new AmazonS3Client(Config.Instance.Aws.Credentials, Config.Instance.Aws.RegionEndpoint);
    var buckets = await s3.ListBucketsAsync();

    foreach (var s3Bucket in buckets.Buckets)
    {
        if (s3Bucket.BucketName.StartsWith("mybucket-"))
        {
            log.Information("Bucket => {BucketName}", s3Bucket.BucketName);

            ListObjectsResponse objects;
            try
            {
                objects = await s3.ListObjectsAsync(s3Bucket.BucketName);
            }
            catch
            {
                log.Error("Error getting objects. Bucket => {BucketName}", s3Bucket.BucketName);
                continue;
            }

            // ForEachAsync (4 is how many tasks you want to run in parallel)
            await objects.S3Objects.ForEachAsync(4, async s3Object =>
            {
                try
                {
                    log.Information("Bucket => {BucketName} => {Key}", s3Bucket.BucketName, s3Object.Key);
                    await s3.DeleteObjectAsync(s3Bucket.BucketName, s3Object.Key);
                }
                catch
                {
                    log.Error("Error deleting bucket {BucketName} object {Key}", s3Bucket.BucketName, s3Object.Key);
                }
            });

            try
            {
                await s3.DeleteBucketAsync(s3Bucket.BucketName);
            }
            catch
            {
                log.Error("Error deleting bucket {BucketName}", s3Bucket.BucketName);
            }
        }
    }
}).Wait();

2

समस्या यह थी कि asyncकीवर्ड को लैम्ब्डा के सामने आने की आवश्यकता है, न कि शरीर से पहले:

db.Groups.ToList().ForEach(async (i) => {
    await GetAdminsFromGroup(i.Gid);
});

35
-1 के अनावश्यक और सूक्ष्म उपयोग के लिए async void। इस दृष्टिकोण में अपवाद से निपटने और जानने के आस-पास की समस्याएं हैं जब अतुल्यकालिक संचालन पूरा हो जाता है।
स्टीफन क्लीयर

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