programing

여러 작업에 비동기/대기 사용

sourcejob 2023. 5. 14. 10:35
반응형

여러 작업에 비동기/대기 사용

저는 완전히 비동기식인 API 클라이언트를 사용하고 있습니다. 즉, 각 작업이 반환됩니다.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 비동기/대기 연산자를 사용하여 여러 작업을 시작하고 모든 작업이 완료될 때까지 기다리는 정확하고 가장 효율적인 방법은 무엇입니까?

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 요청을 발행하여 각각 완료되는 대로 콘솔에 쓰기를 기대합니다.

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

이를 뒷받침하기 위해 다음은 모든 대안과 장점/단점을 설명하는 자세한 블로그 게시물입니다.ASP를 사용한 동시 비동기 I/O 방법 및 위치NET 웹 API

저는 문제에 제시된 방법들의 결과와 합격된 답이 궁금해서 시험해 보았습니다.

코드는 다음과 같습니다.

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.

사용할 수 있습니다.Task.WhenAll기능을 사용할 수 있으며, 원하는 수의 작업을 전달할 수 있습니다.Task.WhenAll모든 작업이 완료되면 완료될 새 작업을 반환합니다.다음 시간에 비동기식으로 기다리십시오.Task.WhenAllUI 스레드를 차단하지 않으려면:

public async Task DoSomethingAsync() {
    Task[] tasks = new Task[numTasks];
    for (int i = 0; i < numTasks; i++)
    {
        tasks[i] = DoChildTaskAsync();
    }
    await Task.WhenAll(tasks);
    // Code here will execute on UI thread
}

호출하는 API가 비동기이기 때문에Parallel.ForEach버전은 말이 안 됩니다.사용하면 안 됩니다..Wait에서WaitAll그 이후 버전은 병렬성을 잃을 수 있습니다. 호출자가 비동기인 경우 다른 대안이 사용 중입니다.Task.WhenAll한 후에Select그리고.ToArray작업 배열을 생성합니다.두 번째 대안은 Rx 2.0을 사용하는 것입니다.

Parallel.ForEach사용자 정의 작업자 목록과 비작업자 목록이 필요합니다. Action각 작업자와 함께 수행합니다.

Task.WaitAll그리고.Task.WhenAll을 요구합니다.List<Task>기본적으로 비동기식입니다.

나는 Ria and DP의 반응이 차이를 이해하는 데 매우 유용하다는 것을 알았지만, 그것은 수정이 필요합니다.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.

질문은 10살이고 OP는 C# 5에 대해 묻고 있었습니다.

오늘부로 옵션이 하나 더 있습니다. 바로 병렬입니다.에서 소개된 각 비동기 메서드의 경우.NET 6.

다음은 OP의 코드를 기반으로 한 예입니다.

int[] ids = new[] { 1, 2, 3, 4, 5 };
await Parallel.ForEachAsync(ids, async (i,token) => await DoSomething(1, i, blogClient));

이것은 완전히 비동기식이며 스레드를 차단하지 않습니다.

또한 작업보다 더 나을 수도 있습니다.모두 대기 및 작업.병렬로 실행되는 스레드 수를 제한하지 않기 때문에 모두가 접근하는 경우.따라서 대용량 어레이를 사용하면 RAM을 모두 사용할 수 있습니다. 병렬입니다.각 비동기의 경우 병렬화 정도를 다음과 같이 지정할 수 있습니다.

var options = new ParallelOptions { MaxDegreeOfParallelism = 4 };

await Parallel.ForEachAsync(ids, options, async (i,token) => await DoSomething(1, i, blogClient));

이렇게 하면 4개의 스레드만 병렬로 실행됩니다.

모든 답은 동일한 기능을 실행하는 것입니다.

다음 코드는 다른 기능을 호출하는 데 사용됩니다.그냥 당신의 규칙을 둬라.Task.Run()에서 어이내에와통수있할습다니화서레로 합니다.Task.WhenAll():

await Task.WhenAll(new Task[] { 
    Task.Run(() => Func1(args)),
    Task.Run(() => Func2(args))
});

위의 모든 훌륭한 답변에 덧붙이고 싶습니다. 만약 당신이 도서관을 쓴다면 그것은 사용하기에 좋은 관행입니다.ConfigureAwait(false)그리고 여기서 말한 것처럼 더 나은 성과를 얻을 수 있습니다.

따라서 이 스니펫이 더 나은 것 같습니다.

 public static async Task DoWork() 
 {
     int[] ids = new[] { 1, 2, 3, 4, 5 };
     await Task.WhenAll(ids.Select(i => DoSomething(1, i))).ConfigureAwait(false);
 }

전체 바이올린 링크입니다.

언급URL : https://stackoverflow.com/questions/12337671/using-async-await-for-multiple-tasks

반응형