여러 비동기 서비스를 동시에 호출


17

서로 의존하지 않는 비동기 REST 서비스가 거의 없습니다. 즉, Service1의 응답을 "대기"하는 동안 Service2, Service3 등을 호출 할 수 있습니다.

예를 들어 아래 코드를 참조하십시오.

var service1Response = await HttpService1Async();
var service2Response = await HttpService2Async();

// Use service1Response and service2Response

이제 service2Response의존하지 않으며 service1Response독립적으로 가져올 수 있습니다. 따라서 두 번째 서비스를 호출하기 위해 첫 번째 서비스의 응답을 기다릴 필요가 없습니다.

Parallel.ForEachCPU 바인딩 작업이 아니기 때문에 여기서 사용할 수 없다고 생각 합니다.

이 두 연산을 병렬로 호출하기 위해 use 호출 할 수 Task.WhenAll있습니까? 내가 사용하는 한 가지 문제 Task.WhenAll는 결과를 반환하지 않는다는 것입니다. 모든 작업이 이미 완료되었고 응답을 가져 오기 위해 필요한 모든 작업을 수행 task.Result한 후 호출 한 후 호출 할 수 있습니다 Task.WhenAll.

샘플 코드 :

var task1 = HttpService1Async();
var task2 = HttpService2Async();

await Task.WhenAll(task1, task2)

var result1 = task1.Result;
var result2 = task2.Result;

// Use result1 and result2

이 코드는 성능면에서 첫 번째 코드보다 낫습니까? 다른 방법을 사용할 수 있습니까?


I do not think I can use Parallel.ForEach here since it is not CPU bound operation-나는 거기에 논리가 보이지 않습니다. 동시성은 동시성입니다.
— Robert Harvey

3
@RobertHarvey 나는이 맥락에서 Parallel.ForEach새로운 스레드를 생성하는 반면 async await하나의 스레드에서 모든 작업을 수행 한다는 우려가 있다고 생각합니다 .
— MetaFight

@ Ankit 코드가 차단하기에 적합한 경우 하나에 의존합니다. 두 번째 예는 두 응답이 모두 준비 될 때까지 차단됩니다. 아마도 첫 번째 예제는 코드 await가 준비되기 전에 응답 ( ) 을 사용하려고 할 때 논리적으로 만 차단합니다 .
— MetaFight

두 서비스 응답을 모두 소비하는 코드의 덜 추상적 인 예를 제공 한 경우보다 만족스러운 답변을 제공하는 것이 더 쉬울 수 있습니다.
— MetaFight

내 두 번째 예에서는 @MetaFight 내가하고 있어요 WhenAll내가 전에 Result.Result가 호출되기 전에 모든 작업을 완료한다는 생각과 함께. Task.Result는 호출 스레드를 차단하므로 작업이 실제로 완료된 후에 호출하면 즉시 결과를 반환한다고 가정합니다. 이해를 확인하고 싶습니다.
— Ankit Vijay

답변:


17

Task.WhenAll 사용하면 하나의 문제는 결과를 반환하지 않는다는 것입니다.

그러나 그것은 수행 결과를 반환합니다. 그것들은 모두 공통 유형의 배열에있을 것이므로 결과를 사용하는 것이 항상 유용한 것은 아닙니다 . 결과를 원하는 배열에 해당하는 항목을 찾아서 Task잠재적으로 캐스트해야합니다. 실제 유형 이므로이 컨텍스트에서 가장 쉽고 읽기 쉬운 접근법은 아니지만 모든 작업의 ​​모든 결과를 원할 때 공통 유형은 처리하려는 유형입니다. .

결과를 가져 오려면 Task.WhenAll을 호출 한 후 task.Result를 호출 할 수 있습니다. 모든 작업이 이미 완료되어 응답을 가져와야하기 때문에 모두?

네, 그렇게 할 수 있습니다. 당신은 await그들 도 할 수 있습니다 ( await결함이있는 작업에서 예외를 풀지 Result만 집계 예외를 throw하지만 그렇지 않으면 동일합니다).

이 코드는 성능면에서 첫 번째 코드보다 낫습니까?

두 작업을 동시에 수행하는 것이 아니라 두 작업을 동시에 수행합니다. 그것이 더 나은지 나쁜지는 기본 작업이 무엇인지에 달려 있습니다. 기본 작업이 "디스크에서 파일 읽기"인 경우 디스크 헤드가 하나만 있고 주어진 시간에 한 곳에만있을 수 있으므로 병렬로 수행하는 것이 느려질 수 있습니다. 두 파일 사이를 뛰어 다니는 것은 한 파일을 읽은 다음 다른 파일을 읽는 것보다 느립니다. 반면, 작업이 "일부 네트워크 요청을 수행"하는 경우 (여기서와 같이) 응답을 기다릴 수 있기 때문에 작업이 더 빠를 수 있습니다 (최소한 특정 수의 동시 요청 수). 보류중인 다른 네트워크 요청이있을 때와 마찬가지로 다른 네트워크 컴퓨터에서도 빠르게. 당신이 그것을 알고 싶다면

다른 방법을 사용할 수 있습니까?

당신이 알고있는 당신에게하지 않는 것이 중요합니다 경우 모든 할 수 있습니다 단순히 당신이 아니라 바로 첫 번째보다 병행하고있는 모든 작업 사이에 던져진 예외를 await하지 않고 작업을 WhenAll전혀. 있는 유일한 방법은 WhenAll당신이 데 제공 AggregateException하지 않고 첫 번째 고장 난 작업을 명중 할 때 던지는 것보다, 모든 고장 난 작업에서 모든 단일 제외합니다. 다음과 같이 간단합니다.

var task1 = HttpService1Async();
var task2 = HttpService2Async();

var result1 = await task1;
var result2 = await task2;

그것은 동시에 동시에 작업을 실행하지 않습니다. 각 작업이 순차적으로 완료되기를 기다리고 있습니다. 성능 코드에 신경 쓰지 않으면 완전히 괜찮습니다.
— Rick O'Shea

3
@ RickO'Shea 순차적으로 작업을 시작 합니다. 이 것 이 * 시작 후 두 번째 작업을 시작 첫번째 작업을. 그러나 비동기 작업을 시작 하는 것은 기본적으로 순간적이어야합니다 (그렇지 않으면 실제로 비동기 적이 지 않으며 해당 방법의 버그입니다). 하나를 시작한 다음 다른 하나를 시작한 후에는 첫 번째 완료 후 두 번째 완료까지 계속되지 않습니다. 두 번째를 시작하기 전에 첫 번째가 끝나기를 기다리는 것은 없기 때문에 동시에 실행하는 것을 막는 것은 없습니다 (병렬로 실행하는 것과 동일).
— Servy

@Servy 나는 그것이 사실이라고 생각하지 않습니다. 두 개의 비동기 작업 내부에 로깅을 추가하여 각각 약 1 초가 걸리고 (http 호출) 제안한대로 호출하고 충분한 task1 시작 및 종료 한 다음 task2 시작 및 종료했는지 확인하십시오.
— 매트 Frear

@MattFrear 그러면이 메서드는 실제로 비동기식이 아닙니다. 동기식이었습니다. 정의에 따르면 비동기 메서드는 작업이 실제로 완료된 후 반환하지 않고 즉시 반환됩니다.
— Servy

@Servy 정의에 따르면 await는 다음 행을 실행하기 전에 비동기 작업이 완료 될 때까지 기다립니다. 그렇지 않습니까?
— Matt Frear

0

다음은 SemaphoreSlim을 사용하고 최대 병렬 처리 수준을 설정할 수있는 확장 방법입니다.

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

-2

둘 중 하나를 사용할 수 있습니다

Parallel.Invoke(() =>
{
    HttpService1Async();
},
() =>
{   
    HttpService2Async();
});

또는

Task task1 = Task.Run(() => HttpService1Async());
Task task2 = Task.Run(() => HttpService2Async());

//If you wish, you can wait for a particular task to return here like this:
task1.Wait();

왜 다운 보트인가?
— user1451111
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.