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


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.