একাধিক কাজের জন্য অ্যাসিঙ্ক / অপেক্ষা করুন


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);
}

সি # 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
@ সার্গ শেভচেনকো সমস্যাটি হল যে তাঁর সমান্তরাল.ফোরএকটি অবিচ্ছিন্নভাবে সম্পন্ন হয়েছে (উত্তর দেখুন) - তিনি জিজ্ঞাসা করছেন যে সমান্তরালভাবে অ্যাসিঙ্ক কোড চালানোর তার চেষ্টাটি সঠিক কিনা, দুটি সমাধানের প্রচেষ্টা দিচ্ছে, এবং যদি অন্যটির চেয়ে ভাল হয় (এবং সম্ভবত কেন তাই হয়) )।
আনোরজাকেন

উত্তর:


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

যদিও আপনি উপরের কোডের সাথে সমান্তরালভাবে অপারেশনগুলি পরিচালনা করেন, এই কোডটি প্রতিটি অপারেশন চলমান প্রতিটি থ্রেডকে অবরুদ্ধ করে। উদাহরণস্বরূপ, যদি নেটওয়ার্ক কলটিতে 2 সেকেন্ড সময় লাগে, প্রতিটি থ্রেড 2 সেকেন্ডের জন্য স্তব্ধ থাকে / অপেক্ষার অপেক্ষা রাখে কিছু না করে।

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)));
}

এটির ব্যাক আপ করার জন্য, এখানে সমস্ত বিকল্প এবং তার সুবিধাগুলি / অসুবিধাগুলির মধ্য দিয়ে চলছে একটি বিস্তারিত ব্লগ পোস্ট: এএসপি.নেট ওয়েব এপিআই সহ সাম্প্রতিক অ্যাসিঙ্ক্রোনাস আই / ও কীভাবে এবং কোথায় রয়েছে Where


31
"উপরের কোডটি WaitAllথ্রেডগুলিও ব্লক করে" - এটি কেবল একটি থ্রেডকে ব্লক করে না , যেটি বলে WaitAll?
রোলিং

5
@ ডকুমেন্টেশন রোলিংয়ে বলা হয়েছে যে "টাইপ: সিস্টেম.ট্রেডিং.টাস্কস.টাস্ক [] টাস্কের একটি অ্যারে যার উপর অপেক্ষা করা উচিত" " সুতরাং, এটি সমস্ত থ্রেড ব্লক করে।
মিক্স্সিফয়েড

30
@ মিক্স্সিফায়ড: আপনার উদ্ধৃত বিটের অর্থ এই নয় যে এটি সমস্ত থ্রেডকে ব্লক করে। সরবরাহকৃত কাজগুলি চলমান থাকাকালীন এটি কেবল কলিং থ্রেডকে ব্লক করে। কীভাবে এই কাজগুলি চালানো হয় তা শিডিয়ুলারের উপর নির্ভর করে। সাধারণত প্রতিটি কাজ শেষ হওয়ার পরে এটি যে থ্রেডে চলছিল তা পুলটিতে ফিরে আসবে। অন্য থ্রেড সম্পূর্ণ না হওয়া অবধি অবরুদ্ধ থাকবে না।
মুসৌল

3
@ টগবার্ক, আমি এটি যেভাবে বুঝতে পারি, "ক্লাসিক" টাস্ক পদ্ধতি এবং অ্যাসিঙ্ক সহযোগীদের মধ্যে একমাত্র পার্থক্য হ'ল কোনও কাজ যখন শুরু হয় এবং এটি চলমান শেষ করে তখন কীভাবে তারা থ্রেডগুলির সাথে যোগাযোগ করে। ডিফল্ট শিডিয়ুলারের অধীনে ক্লাসিক পদ্ধতিটি সেই সময়ের মধ্যে একটি থ্রেডকে ঝুলিয়ে দেবে (যদিও এটি "ঘুমন্ত" হয়), তবে অ্যাসিঙ্কগুলি তা করবে না। এই সময়ের বাইরে কোনও পার্থক্য নেই, যেমন কাজটি তফসিলযুক্ত তবে শুরু হয়নি, এবং এটি শেষ হয়ে গেলেও এটির কলার এখনও অপেক্ষা করছে।
মুসৌল

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
@ সেরজসাগান আমার প্রাথমিক ধারণাটি কেবল প্রতিটি ক্ষেত্রে শ্রমিকদের একযোগে শুরু করা হচ্ছে তা যাচাই করার জন্য ছিল, তবে আমি পরীক্ষার স্বচ্ছতা বাড়ানোর জন্য টাইম স্ট্যাম্প যুক্ত করেছি। পরামর্শের জন্য ধন্যবাদ.
RiaanDP

পরীক্ষার জন্য আপনাকে ধন্যবাদ। তবে এটি কিছুটা বিশ্রী মনে হয় যে আপনি থ্রেড চালাচ্ছেন "" কর্মী থ্রেড "থেকে পৃথক থ্রেডে ঘুমান s এই ক্ষেত্রে এটি গুরুত্বপূর্ণ নয়, তবে এটি টাস্কের পক্ষে আরও বেশি অর্থবোধ করতে পারে না। আমরা যদি কম্পিউটারের কাজ অনুকরণ করি তবে কর্মী থ্রেডগুলি চালান, বা আমরা যদি i / o সিমুলেট করি তবে ঘুমের পরিবর্তে ডেলি? আপনার মতামত কী হবে তা কেবল খতিয়ে দেখছি।
অ্যানোরজাকেন

24

যে API টি আপনি কল করছেন এটি async, Parallel.ForEachসংস্করণটি খুব বেশি অর্থ দেয় না। আপনি এই ব্যবহার উচিত না .WaitWaitAllসংস্করণ থেকে যে উপমা আরেকটি বিকল্প হারাবে হলে কলার ASYNC ব্যবহার করছে Task.WhenAllকরছেন পরে Selectএবং ToArrayকাজগুলো অ্যারে তৈরি করতে। দ্বিতীয় বিকল্পটি Rx 2.0 ব্যবহার করছে


10

আপনি nTask.WhenAll ফাংশনটি পাস করতে পারেন এমন ফাংশন ব্যবহার করতে পারেন ; যখন সমস্ত কর্ম আপনি প্রেরণ সমাপ্তির যা রান একটি টাস্ক ফিরে আসবে সম্পূর্ণ। আপনাকে অবিচ্ছিন্নভাবে অপেক্ষা করতে হবে যাতে আপনি আপনার ইউআই থ্রেডটি আটকাবেন না:Task.WhenAllTask.WhenAllTask.WhenAll

   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)));
        }
    }
}

ফলাফল আউটপুট নীচে। ফাঁসির সময় তুলনাযোগ্য ara আমার কম্পিউটার সাপ্তাহিক অ্যান্টি ভাইরাস স্ক্যান করার সময় আমি এই পরীক্ষাটি চালিয়েছিলাম। পরীক্ষার ক্রম পরিবর্তন করে তাদের উপর কার্যকরকরণের সময়কে পরিবর্তন করে।

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.
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.