अतुल्यकालिक लैम्ब्डा के साथ समानांतर फॉरेक्स


138

मैं समानांतर में एक संग्रह को संभालना चाहूंगा, लेकिन मुझे इसे लागू करने में परेशानी हो रही है और इसलिए मैं कुछ मदद की उम्मीद कर रहा हूं।

यदि मैं समानांतर # लूपडा के लैम्ब्डा के भीतर C # में async के रूप में चिह्नित एक विधि को कॉल करना चाहता हूं तो परेशानी पैदा होती है। उदाहरण के लिए:

var bag = new ConcurrentBag<object>();
Parallel.ForEach(myCollection, async item =>
{
  // some pre stuff
  var response = await GetData(item);
  bag.Add(response);
  // some post stuff
}
var count = bag.Count;

यह समस्या 0 होने के कारण होती है, क्योंकि बनाए गए सभी थ्रेड प्रभावी रूप से सिर्फ बैकग्राउंड थ्रेड होते हैं और Parallel.ForEachकॉल पूरा होने का इंतजार नहीं करता है। यदि मैं async कीवर्ड हटाता हूं, तो विधि इस प्रकार दिखाई देती है:

var bag = new ConcurrentBag<object>();
Parallel.ForEach(myCollection, item =>
{
  // some pre stuff
  var responseTask = await GetData(item);
  responseTask.Wait();
  var response = responseTask.Result;
  bag.Add(response);
  // some post stuff
}
var count = bag.Count;

यह काम करता है, लेकिन यह पूरी तरह से प्रतीक्षित चतुराई को निष्क्रिय कर देता है और मुझे कुछ मैनुअल अपवाद को संभालना पड़ता है .. (संक्षिप्तता के लिए हटाया गया)।

मैं एक Parallel.ForEachलूप कैसे लागू कर सकता हूं , जो लंबोदर के भीतर प्रतीक्षित कीवर्ड का उपयोग करता है? क्या यह संभव है?

Parallel.ForEach पद्धति का प्रोटोटाइप एक Action<T>पैरामीटर के रूप में लेता है , लेकिन मैं चाहता हूं कि यह मेरे अतुल्यकालिक लैम्बडा की प्रतीक्षा करे।


1
मुझे लगता है कि आपको अपने दूसरे कोड ब्लॉक awaitसे हटाने का मतलब था await GetData(item)क्योंकि यह एक संकलन त्रुटि के रूप में होगा।
जोश एम।

जवाबों:


186

यदि आप सरल समानता चाहते हैं, तो आप यह कर सकते हैं:

var bag = new ConcurrentBag<object>();
var tasks = myCollection.Select(async item =>
{
  // some pre stuff
  var response = await GetData(item);
  bag.Add(response);
  // some post stuff
});
await Task.WhenAll(tasks);
var count = bag.Count;

यदि आपको कुछ और जटिल चाहिए, तो स्टीफन टूब की ForEachAsyncपोस्ट देखें


46
संभवतः एक थ्रॉटलिंग तंत्र की आवश्यकता है। यह तुरंत ही कई कार्यों का निर्माण करेगा क्योंकि ऐसी वस्तुएं हैं जो 10k नेटवर्क अनुरोधों और इस तरह समाप्त हो सकती हैं।
usr

10
@usr स्टीफन टूब के लेख में अंतिम उदाहरण यह है कि।
23

@svick मैं उस अंतिम नमूने पर हैरान था। यह मुझे दिखता है कि यह मेरे लिए और अधिक कार्य बनाने के लिए कार्यों के भार को सीमित करता है, लेकिन वे सभी एन-मास शुरू करते हैं।
ल्यूक पुप्लेट

2
@ ल्यूकपलेट यह dopकार्य करता है और उनमें से प्रत्येक श्रृंखला में इनपुट संग्रह के कुछ सबसेट को संसाधित करता है।
svick

4
@Afshin_Zavvar: यदि आप परिणाम के Task.Runबिना कॉल awaitकरते हैं, तो यह सिर्फ थ्रेड पूल पर आग और भूल काम फेंक रहा है। यह लगभग हमेशा एक गलती है।
स्टीफन क्लीयर

74

आप AsyncEnumerator NuGet पैकेजParallelForEachAsync से एक्सटेंशन विधि का उपयोग कर सकते हैं :

using Dasync.Collections;

var bag = new ConcurrentBag<object>();
await myCollection.ParallelForEachAsync(async item =>
{
  // some pre stuff
  var response = await GetData(item);
  bag.Add(response);
  // some post stuff
}, maxDegreeOfParallelism: 10);
var count = bag.Count;

1
यह आपका पैकेज है? मैंने आपको अभी कुछ स्थानों पर यह पोस्ट करते देखा है? : डी ओह रुको .. आपका नाम पैकेज पर है: डी +1
पिरोत कुला

17
@ppumkin, हाँ, यह मेरा है। मैंने इस समस्या को बार-बार देखा है, इसलिए इसे सबसे सरल तरीके से हल करने का फैसला किया और दूसरों को भी संघर्ष से मुक्त कर दिया :)
सर्ज सेमेनोव

धन्यवाद .. यह निश्चित रूप से समझ में आता है और मुझे बड़ा समय निकालने में मदद करता है!
पायोटर कुला

2
आपके पास एक टाइपो है: maxDegreeOfParallelism>maxDegreeOfParalellism
शिरान दार

3
सही वर्तनी वास्तव में maxDegreeOfParallelism है, हालांकि @ ShiranDror की टिप्पणी में कुछ है - आपके पैकेज में आपने चर maxDegreeOfParalellism को गलती से बुलाया (और इसलिए आपका उद्धरण कोड तब तक संकलित नहीं होगा जब तक कि आप इसे बदल न दें ..)
BornToCode

17

साथ SemaphoreSlimआप समानांतरवाद नियंत्रण हासिल कर सकते हैं।

var bag = new ConcurrentBag<object>();
var maxParallel = 20;
var throttler = new SemaphoreSlim(initialCount: maxParallel);
var tasks = myCollection.Select(async item =>
{
  try
  {
     await throttler.WaitAsync();
     var response = await GetData(item);
     bag.Add(response);
  }
  finally
  {
     throttler.Release();
  }
});
await Task.WhenAll(tasks);
var count = bag.Count;

3

ParallelForEach async का मेरा हल्का कार्यान्वयन।

विशेषताएं:

  1. थ्रॉटलिंग (समानता की अधिकतम डिग्री)।
  2. अपवाद से निपटने (एकत्रीकरण अपवाद पूरा होने पर फेंक दिया जाएगा)।
  3. मेमोरी कुशल (कार्यों की सूची संग्रहीत करने की आवश्यकता नहीं)।

public static class AsyncEx
{
    public static async Task ParallelForEachAsync<T>(this IEnumerable<T> source, Func<T, Task> asyncAction, int maxDegreeOfParallelism = 10)
    {
        var semaphoreSlim = new SemaphoreSlim(maxDegreeOfParallelism);
        var tcs = new TaskCompletionSource<object>();
        var exceptions = new ConcurrentBag<Exception>();
        bool addingCompleted = false;

        foreach (T item in source)
        {
            await semaphoreSlim.WaitAsync();
            asyncAction(item).ContinueWith(t =>
            {
                semaphoreSlim.Release();

                if (t.Exception != null)
                {
                    exceptions.Add(t.Exception);
                }

                if (Volatile.Read(ref addingCompleted) && semaphoreSlim.CurrentCount == maxDegreeOfParallelism)
                {
                    tcs.SetResult(null);
                }
            });
        }

        Volatile.Write(ref addingCompleted, true);
        await tcs.Task;
        if (exceptions.Count > 0)
        {
            throw new AggregateException(exceptions);
        }
    }
}

उपयोग उदाहरण:

await Enumerable.Range(1, 10000).ParallelForEachAsync(async (i) =>
{
    var data = await GetData(i);
}, maxDegreeOfParallelism: 100);

2

मैंने इसके लिए एक विस्तार विधि बनाई है जो सेमाफोरस्लीम का उपयोग करता है और अधिकतम समानता की डिग्री निर्धारित करने की भी अनुमति देता है

    /// <summary>
    /// Concurrently Executes async actions for each item of <see cref="IEnumerable<typeparamref name="T"/>
    /// </summary>
    /// <typeparam name="T">Type of IEnumerable</typeparam>
    /// <param name="enumerable">instance of <see cref="IEnumerable<typeparamref name="T"/>"/></param>
    /// <param name="action">an async <see cref="Action" /> to execute</param>
    /// <param name="maxDegreeOfParallelism">Optional, An integer that represents the maximum degree of parallelism,
    /// Must be grater than 0</param>
    /// <returns>A Task representing an async operation</returns>
    /// <exception cref="ArgumentOutOfRangeException">If the maxActionsToRunInParallel is less than 1</exception>
    public static async Task ForEachAsyncConcurrent<T>(
        this IEnumerable<T> enumerable,
        Func<T, Task> action,
        int? maxDegreeOfParallelism = null)
    {
        if (maxDegreeOfParallelism.HasValue)
        {
            using (var semaphoreSlim = new SemaphoreSlim(
                maxDegreeOfParallelism.Value, maxDegreeOfParallelism.Value))
            {
                var tasksWithThrottler = new List<Task>();

                foreach (var item in enumerable)
                {
                    // Increment the number of currently running tasks and wait if they are more than limit.
                    await semaphoreSlim.WaitAsync();

                    tasksWithThrottler.Add(Task.Run(async () =>
                    {
                        await action(item).ContinueWith(res =>
                        {
                            // action is completed, so decrement the number of currently running tasks
                            semaphoreSlim.Release();
                        });
                    }));
                }

                // Wait for all tasks to complete.
                await Task.WhenAll(tasksWithThrottler.ToArray());
            }
        }
        else
        {
            await Task.WhenAll(enumerable.Select(item => action(item)));
        }
    }

नमूना उपयोग:

await enumerable.ForEachAsyncConcurrent(
    async item =>
    {
        await SomeAsyncMethod(item);
    },
    5);

'उपयोग' करने से मदद नहीं मिलेगी। फॉरवर्ड लूप अनिश्चितकाल के लिए सेमाफोन का इंतजार कर रहा होगा। बस इस सरल कोड को आज़माएं जो समस्या को पुन: पेश करता है: Enumerable.Range (1, 4) .orEachAsyncConcurrent (async (i) => {Console.WriteLine (i)) का इंतजार करें, नया इंसेप्शन फेंकें ("परीक्षण अपवाद");} maxDegreeOfParallelism; 2);
nicolay.anykienko

@ nicolay.anykienko आप # 2 के बारे में सही हैं। उस मेमोरी प्रॉब्लम को WorkWithThrottler.RemoveAll (x => x.IsCompleted) जोड़कर हल किया जा सकता है;
पूछते हैं

1
मैंने इसे अपने कोड में आज़माया है और अगर मैं maxDegreeOfParallelism कोड डेडलॉक को शून्य नहीं करता। यहाँ आप पुन: पेश करने के लिए सभी कोड देख सकते हैं: stackoverflow.com/questions/58793118/…
मास्सिमो सावाज़ज़ी
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.