कई कार्यों के लिए async / प्रतीक्षा का उपयोग करना


406

मैं एक एपीआई क्लाइंट का उपयोग कर रहा हूं जो पूरी तरह से अतुल्य है, यानी प्रत्येक ऑपरेशन या तो रिटर्न करता है Taskया Task<T>, जैसे:

static async Task DoSomething(int siteId, int postId, IBlogClient client)
{
    await client.DeletePost(siteId, postId); // call API client
    Console.WriteLine("Deleted post {0}.", siteId);
}

C # 5 async / प्रतीक्षारत ऑपरेटरों का उपयोग करना, कई कार्यों को शुरू करने और उन सभी को पूरा करने के लिए प्रतीक्षा करने का सही / सबसे कुशल तरीका क्या है:

int[] ids = new[] { 1, 2, 3, 4, 5 };
Parallel.ForEach(ids, i => DoSomething(1, i, blogClient).Wait());

या:

int[] ids = new[] { 1, 2, 3, 4, 5 };
Task.WaitAll(ids.Select(i => DoSomething(1, i, blogClient)).ToArray());

चूंकि API क्लाइंट आंतरिक रूप से HttpClient का उपयोग कर रहा है, इसलिए मैं अपेक्षा करूंगा कि यह तुरंत 5 HTTP अनुरोधों को जारी करे, कंसोल पर लिखते हुए प्रत्येक को पूरा करता है।


और क्या समस्या है?
सर्ग शेवचेंको

1
@SergShevchenko समस्या यह है कि उनके Parallel.ForEach को अनिश्चित रूप से किया जाता है (उत्तर देखें) - वह पूछ रहा है कि क्या समानांतर में async कोड चलाने का उसका प्रयास सही है, दो समाधान प्रयासों की पेशकश करते हैं, और यदि एक दूसरे से बेहतर है (और संभवतः ऐसा क्यों है )।
एर्ज़ोकेन

जवाबों:


572
int[] ids = new[] { 1, 2, 3, 4, 5 };
Parallel.ForEach(ids, i => DoSomething(1, i, blogClient).Wait());

यद्यपि आप उपरोक्त कोड के समानांतर ऑपरेशन चलाते हैं, यह कोड प्रत्येक थ्रेड को ब्लॉक करता है जो प्रत्येक ऑपरेशन पर चलता है। उदाहरण के लिए, यदि नेटवर्क कॉल में 2 सेकंड लगते हैं, तो प्रत्येक थ्रेड 2 सेकंड w / o के लिए लटका रहता है, लेकिन कुछ भी कर रहा है।

int[] ids = new[] { 1, 2, 3, 4, 5 };
Task.WaitAll(ids.Select(i => DoSomething(1, i, blogClient)).ToArray());

दूसरी ओर, उपरोक्त कोड WaitAllभी थ्रेड्स को ब्लॉक करता है और ऑपरेशन समाप्त होने तक आपके थ्रेड्स किसी अन्य कार्य को संसाधित करने के लिए स्वतंत्र नहीं होंगे।

अनुशंसित दृष्टिकोण

मैं पसंद करूंगा WhenAllजो समानांतर रूप से आपके कार्यों को अतुल्यकालिक रूप से निष्पादित करेगा।

public async Task DoWork() {

    int[] ids = new[] { 1, 2, 3, 4, 5 };
    await Task.WhenAll(ids.Select(i => DoSomething(1, i, blogClient)));
}

वास्तव में, उपरोक्त मामले में, आपको इसकी आवश्यकता भी नहीं है await, आप सीधे उस विधि से वापस आ सकते हैं, जब आपके पास कोई निरंतरता नहीं है:

public Task DoWork() 
{
    int[] ids = new[] { 1, 2, 3, 4, 5 };
    return Task.WhenAll(ids.Select(i => DoSomething(1, i, blogClient)));
}

इसे वापस करने के लिए, यहाँ सभी विकल्पों और उनके फायदों / नुकसान के बारे में एक विस्तृत ब्लॉग पोस्ट दी जा रही है: ASP.NET वेब एपीआई के साथ कैसे और कहाँ समवर्ती अतुल्यकालिक I / O


31
"उपरोक्त कोड WaitAllभी थ्रेड्स को ब्लॉक करता है" - क्या यह केवल एक थ्रेड को ब्लॉक नहीं करता है , जिसे कहा जाता है WaitAll?
Rawling

5
@Rawling दस्तावेज़ीकरण में कहा गया है कि "टाइप करें: System.Threading.Tasks.Task [] टास्क इंस्टेंस की एक सरणी, जिसमें प्रतीक्षा करनी है।" तो, यह सभी थ्रेड्स को ब्लॉक करता है।
मिक्सक्सीफॉइड

30
@Mixxiphoid: आपके द्वारा उद्धृत बिट का मतलब यह नहीं है कि यह सभी थ्रेड्स को ब्लॉक करता है। यह केवल कॉलिंग थ्रेड को ब्लॉक करता है जबकि आपूर्ति किए गए कार्य चल रहे हैं। उन कार्यों को वास्तव में कैसे चलाया जाता है, यह अनुसूचक पर निर्भर करता है। आमतौर पर प्रत्येक कार्य पूरा होने के बाद, यह जिस थ्रेड पर चल रहा था, वह पूल में वापस आ जाएगा। प्रत्येक धागा तब तक अवरुद्ध नहीं होगा जब तक कि अन्य पूर्ण न हों।
13

3
@tugberk, जिस तरह से मैं इसे समझता हूं, "क्लासिक" टास्क विधियों और Async समकक्षों के बीच एकमात्र अंतर यह है कि वे कैसे थ्रेड्स के साथ बातचीत करते हैं जब कोई कार्य चलना शुरू होता है और यह चल रहा है। एक डिफ़ॉल्ट अनुसूचक के तहत क्लासिक विधि उस अवधि के दौरान एक धागा हॉग करेगी (भले ही यह "सो रही है"), जबकि एसिंक्स नहीं होंगे। उस अवधि के बाहर कोई अंतर नहीं है, अर्थात कार्य शेड्यूल किया गया है लेकिन शुरू नहीं हुआ है, और जब यह पूरा हो गया है, लेकिन यह अभी भी इंतजार कर रहा है।
मुसलाम

3
@tugberk देखें stackoverflow.com/a/6123432/750216 अंतर यह है कि कॉलिंग थ्रेड अवरुद्ध है या नहीं, बाकी समान है। आप स्पष्ट करने के लिए उत्तर को संपादित करना चाह सकते हैं।
रेज़वान फ़्लेवियस पांडा

45

मैं प्रश्न में प्रदान किए गए तरीकों के परिणामों के साथ-साथ स्वीकृत उत्तर को देखने के लिए उत्सुक था, इसलिए मैंने इसे परीक्षण के लिए रखा।

यहाँ कोड है:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace AsyncTest
{
    class Program
    {
        class Worker
        {
            public int Id;
            public int SleepTimeout;

            public async Task DoWork(DateTime testStart)
            {
                var workerStart = DateTime.Now;
                Console.WriteLine("Worker {0} started on thread {1}, beginning {2} seconds after test start.",
                    Id, Thread.CurrentThread.ManagedThreadId, (workerStart-testStart).TotalSeconds.ToString("F2"));
                await Task.Run(() => Thread.Sleep(SleepTimeout));
                var workerEnd = DateTime.Now;
                Console.WriteLine("Worker {0} stopped; the worker took {1} seconds, and it finished {2} seconds after the test start.",
                   Id, (workerEnd-workerStart).TotalSeconds.ToString("F2"), (workerEnd-testStart).TotalSeconds.ToString("F2"));
            }
        }

        static void Main(string[] args)
        {
            var workers = new List<Worker>
            {
                new Worker { Id = 1, SleepTimeout = 1000 },
                new Worker { Id = 2, SleepTimeout = 2000 },
                new Worker { Id = 3, SleepTimeout = 3000 },
                new Worker { Id = 4, SleepTimeout = 4000 },
                new Worker { Id = 5, SleepTimeout = 5000 },
            };

            var startTime = DateTime.Now;
            Console.WriteLine("Starting test: Parallel.ForEach...");
            PerformTest_ParallelForEach(workers, startTime);
            var endTime = DateTime.Now;
            Console.WriteLine("Test finished after {0} seconds.\n",
                (endTime - startTime).TotalSeconds.ToString("F2"));

            startTime = DateTime.Now;
            Console.WriteLine("Starting test: Task.WaitAll...");
            PerformTest_TaskWaitAll(workers, startTime);
            endTime = DateTime.Now;
            Console.WriteLine("Test finished after {0} seconds.\n",
                (endTime - startTime).TotalSeconds.ToString("F2"));

            startTime = DateTime.Now;
            Console.WriteLine("Starting test: Task.WhenAll...");
            var task = PerformTest_TaskWhenAll(workers, startTime);
            task.Wait();
            endTime = DateTime.Now;
            Console.WriteLine("Test finished after {0} seconds.\n",
                (endTime - startTime).TotalSeconds.ToString("F2"));

            Console.ReadKey();
        }

        static void PerformTest_ParallelForEach(List<Worker> workers, DateTime testStart)
        {
            Parallel.ForEach(workers, worker => worker.DoWork(testStart).Wait());
        }

        static void PerformTest_TaskWaitAll(List<Worker> workers, DateTime testStart)
        {
            Task.WaitAll(workers.Select(worker => worker.DoWork(testStart)).ToArray());
        }

        static Task PerformTest_TaskWhenAll(List<Worker> workers, DateTime testStart)
        {
            return Task.WhenAll(workers.Select(worker => worker.DoWork(testStart)));
        }
    }
}

और परिणामी आउटपुट:

Starting test: Parallel.ForEach...
Worker 1 started on thread 1, beginning 0.21 seconds after test start.
Worker 4 started on thread 5, beginning 0.21 seconds after test start.
Worker 2 started on thread 3, beginning 0.21 seconds after test start.
Worker 5 started on thread 6, beginning 0.21 seconds after test start.
Worker 3 started on thread 4, beginning 0.21 seconds after test start.
Worker 1 stopped; the worker took 1.90 seconds, and it finished 2.11 seconds after the test start.
Worker 2 stopped; the worker took 3.89 seconds, and it finished 4.10 seconds after the test start.
Worker 3 stopped; the worker took 5.89 seconds, and it finished 6.10 seconds after the test start.
Worker 4 stopped; the worker took 5.90 seconds, and it finished 6.11 seconds after the test start.
Worker 5 stopped; the worker took 8.89 seconds, and it finished 9.10 seconds after the test start.
Test finished after 9.10 seconds.

Starting test: Task.WaitAll...
Worker 1 started on thread 1, beginning 0.01 seconds after test start.
Worker 2 started on thread 1, beginning 0.01 seconds after test start.
Worker 3 started on thread 1, beginning 0.01 seconds after test start.
Worker 4 started on thread 1, beginning 0.01 seconds after test start.
Worker 5 started on thread 1, beginning 0.01 seconds after test start.
Worker 1 stopped; the worker took 1.00 seconds, and it finished 1.01 seconds after the test start.
Worker 2 stopped; the worker took 2.00 seconds, and it finished 2.01 seconds after the test start.
Worker 3 stopped; the worker took 3.00 seconds, and it finished 3.01 seconds after the test start.
Worker 4 stopped; the worker took 4.00 seconds, and it finished 4.01 seconds after the test start.
Worker 5 stopped; the worker took 5.00 seconds, and it finished 5.01 seconds after the test start.
Test finished after 5.01 seconds.

Starting test: Task.WhenAll...
Worker 1 started on thread 1, beginning 0.00 seconds after test start.
Worker 2 started on thread 1, beginning 0.00 seconds after test start.
Worker 3 started on thread 1, beginning 0.00 seconds after test start.
Worker 4 started on thread 1, beginning 0.00 seconds after test start.
Worker 5 started on thread 1, beginning 0.00 seconds after test start.
Worker 1 stopped; the worker took 1.00 seconds, and it finished 1.00 seconds after the test start.
Worker 2 stopped; the worker took 2.00 seconds, and it finished 2.00 seconds after the test start.
Worker 3 stopped; the worker took 3.00 seconds, and it finished 3.00 seconds after the test start.
Worker 4 stopped; the worker took 4.00 seconds, and it finished 4.00 seconds after the test start.
Worker 5 stopped; the worker took 5.00 seconds, and it finished 5.00 seconds after the test start.
Test finished after 5.00 seconds.

2
यदि आप इन परिणामों में से प्रत्येक पर समय
लगाते हैं

8
@SerjSagan मेरा शुरुआती विचार सिर्फ यह सत्यापित करने के लिए था कि प्रत्येक मामले में श्रमिकों को समवर्ती रूप से शुरू किया जा रहा है, लेकिन मैंने परीक्षण की स्पष्टता में सुधार करने के लिए समय टिकटों को जोड़ दिया है। सलाह के लिये धन्यवाद।
रियानडीडीपी

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

24

चूंकि आप जिस API को कॉल कर रहे हैं वह async है, Parallel.ForEachसंस्करण बहुत मायने नहीं रखता है। आपको संस्करण .Waitमें उपयोग नहीं WaitAllकरना चाहिए क्योंकि यह समानता खो देगा। यदि कॉल करने के Task.WhenAllबाद async उपयोग कर रहा है Selectऔर ToArrayकार्यों की सरणी उत्पन्न करने के लिए एक और विकल्प है। एक दूसरा विकल्प Rx 2.0 का उपयोग कर रहा है


10

आप Task.WhenAllफ़ंक्शन का उपयोग कर सकते हैं जिसे आप n कार्य पास कर सकते हैं; Task.WhenAllएक ऐसा कार्य लौटाएगा जो पूरा होने पर पूरा होने वाले कार्यों को Task.WhenAllपूरा करता है। आपको अतुल्यकालिक रूप से प्रतीक्षा करनी Task.WhenAllहोगी ताकि आप अपने UI थ्रेड को ब्लॉक न करें:

   public async Task DoSomeThing() {

       var Task[] tasks = new Task[numTasks];
       for(int i = 0; i < numTask; i++)
       {
          tasks[i] = CallSomeAsync();
       }
       await Task.WhenAll(tasks);
       // code that'll execute on UI thread
   }

8

Parallel.ForEachउपयोगकर्ता-परिभाषित श्रमिकों की सूची और एक गैर-एसिंक्स की आवश्यकता होती है Actionप्रत्येक कार्यकर्ता के साथ प्रदर्शन करने के लिए की आवश्यकता होती है।

Task.WaitAllऔर Task.WhenAllएक की आवश्यकता हैList<Task> , जो परिभाषा अतुल्यकालिक हैं।

मुझे अंतर समझने के लिए RiaanDP की प्रतिक्रिया बहुत उपयोगी लगी , लेकिन इसके लिए एक सुधार की आवश्यकता है Parallel.ForEach। उनकी टिप्पणी का जवाब देने के लिए पर्याप्त प्रतिष्ठा नहीं है, इस प्रकार मेरी अपनी प्रतिक्रिया।

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace AsyncTest
{
    class Program
    {
        class Worker
        {
            public int Id;
            public int SleepTimeout;

            public void DoWork(DateTime testStart)
            {
                var workerStart = DateTime.Now;
                Console.WriteLine("Worker {0} started on thread {1}, beginning {2} seconds after test start.",
                    Id, Thread.CurrentThread.ManagedThreadId, (workerStart - testStart).TotalSeconds.ToString("F2"));
                Thread.Sleep(SleepTimeout);
                var workerEnd = DateTime.Now;
                Console.WriteLine("Worker {0} stopped; the worker took {1} seconds, and it finished {2} seconds after the test start.",
                   Id, (workerEnd - workerStart).TotalSeconds.ToString("F2"), (workerEnd - testStart).TotalSeconds.ToString("F2"));
            }

            public async Task DoWorkAsync(DateTime testStart)
            {
                var workerStart = DateTime.Now;
                Console.WriteLine("Worker {0} started on thread {1}, beginning {2} seconds after test start.",
                    Id, Thread.CurrentThread.ManagedThreadId, (workerStart - testStart).TotalSeconds.ToString("F2"));
                await Task.Run(() => Thread.Sleep(SleepTimeout));
                var workerEnd = DateTime.Now;
                Console.WriteLine("Worker {0} stopped; the worker took {1} seconds, and it finished {2} seconds after the test start.",
                   Id, (workerEnd - workerStart).TotalSeconds.ToString("F2"), (workerEnd - testStart).TotalSeconds.ToString("F2"));
            }
        }

        static void Main(string[] args)
        {
            var workers = new List<Worker>
            {
                new Worker { Id = 1, SleepTimeout = 1000 },
                new Worker { Id = 2, SleepTimeout = 2000 },
                new Worker { Id = 3, SleepTimeout = 3000 },
                new Worker { Id = 4, SleepTimeout = 4000 },
                new Worker { Id = 5, SleepTimeout = 5000 },
            };

            var startTime = DateTime.Now;
            Console.WriteLine("Starting test: Parallel.ForEach...");
            PerformTest_ParallelForEach(workers, startTime);
            var endTime = DateTime.Now;
            Console.WriteLine("Test finished after {0} seconds.\n",
                (endTime - startTime).TotalSeconds.ToString("F2"));

            startTime = DateTime.Now;
            Console.WriteLine("Starting test: Task.WaitAll...");
            PerformTest_TaskWaitAll(workers, startTime);
            endTime = DateTime.Now;
            Console.WriteLine("Test finished after {0} seconds.\n",
                (endTime - startTime).TotalSeconds.ToString("F2"));

            startTime = DateTime.Now;
            Console.WriteLine("Starting test: Task.WhenAll...");
            var task = PerformTest_TaskWhenAll(workers, startTime);
            task.Wait();
            endTime = DateTime.Now;
            Console.WriteLine("Test finished after {0} seconds.\n",
                (endTime - startTime).TotalSeconds.ToString("F2"));

            Console.ReadKey();
        }

        static void PerformTest_ParallelForEach(List<Worker> workers, DateTime testStart)
        {
            Parallel.ForEach(workers, worker => worker.DoWork(testStart));
        }

        static void PerformTest_TaskWaitAll(List<Worker> workers, DateTime testStart)
        {
            Task.WaitAll(workers.Select(worker => worker.DoWorkAsync(testStart)).ToArray());
        }

        static Task PerformTest_TaskWhenAll(List<Worker> workers, DateTime testStart)
        {
            return Task.WhenAll(workers.Select(worker => worker.DoWorkAsync(testStart)));
        }
    }
}

परिणामी आउटपुट नीचे है। निष्पादन समय तुलनीय है। मैंने यह परीक्षण तब चलाया जब मेरा कंप्यूटर साप्ताहिक एंटी वायरस स्कैन कर रहा था। परीक्षणों के क्रम को बदलने से उन पर अमल का समय बदल गया।

Starting test: Parallel.ForEach...
Worker 1 started on thread 9, beginning 0.02 seconds after test start.
Worker 2 started on thread 10, beginning 0.02 seconds after test start.
Worker 3 started on thread 11, beginning 0.02 seconds after test start.
Worker 4 started on thread 13, beginning 0.03 seconds after test start.
Worker 5 started on thread 14, beginning 0.03 seconds after test start.
Worker 1 stopped; the worker took 1.00 seconds, and it finished 1.02 seconds after the test start.
Worker 2 stopped; the worker took 2.00 seconds, and it finished 2.02 seconds after the test start.
Worker 3 stopped; the worker took 3.00 seconds, and it finished 3.03 seconds after the test start.
Worker 4 stopped; the worker took 4.00 seconds, and it finished 4.03 seconds after the test start.
Worker 5 stopped; the worker took 5.00 seconds, and it finished 5.03 seconds after the test start.
Test finished after 5.03 seconds.

Starting test: Task.WaitAll...
Worker 1 started on thread 9, beginning 0.00 seconds after test start.
Worker 2 started on thread 9, beginning 0.00 seconds after test start.
Worker 3 started on thread 9, beginning 0.00 seconds after test start.
Worker 4 started on thread 9, beginning 0.00 seconds after test start.
Worker 5 started on thread 9, beginning 0.01 seconds after test start.
Worker 1 stopped; the worker took 1.00 seconds, and it finished 1.01 seconds after the test start.
Worker 2 stopped; the worker took 2.00 seconds, and it finished 2.01 seconds after the test start.
Worker 3 stopped; the worker took 3.00 seconds, and it finished 3.01 seconds after the test start.
Worker 4 stopped; the worker took 4.00 seconds, and it finished 4.01 seconds after the test start.
Worker 5 stopped; the worker took 5.00 seconds, and it finished 5.01 seconds after the test start.
Test finished after 5.01 seconds.

Starting test: Task.WhenAll...
Worker 1 started on thread 9, beginning 0.00 seconds after test start.
Worker 2 started on thread 9, beginning 0.00 seconds after test start.
Worker 3 started on thread 9, beginning 0.00 seconds after test start.
Worker 4 started on thread 9, beginning 0.00 seconds after test start.
Worker 5 started on thread 9, beginning 0.00 seconds after test start.
Worker 1 stopped; the worker took 1.00 seconds, and it finished 1.00 seconds after the test start.
Worker 2 stopped; the worker took 2.00 seconds, and it finished 2.00 seconds after the test start.
Worker 3 stopped; the worker took 3.00 seconds, and it finished 3.00 seconds after the test start.
Worker 4 stopped; the worker took 4.00 seconds, and it finished 4.00 seconds after the test start.
Worker 5 stopped; the worker took 5.00 seconds, and it finished 5.01 seconds after the test start.
Test finished after 5.01 seconds.
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.