여러 개의 비동기 작업을 실행하고 모두 완료되기를 기다리는 중


265

콘솔 응용 프로그램에서 여러 비동기 작업을 실행하고 추가 처리 전에 모두 완료 될 때까지 기다려야합니다.

많은 기사가 있지만 읽을수록 많이 혼란스러워하는 것 같습니다. 작업 라이브러리의 기본 원칙을 읽고 이해했지만 어딘가에 링크가 명확하게 없습니다.

나는 다른 완료 (시작한 모든 기사에 대한 시나리오 임) 후에 시작되도록 작업을 연결할 수 있지만 모든 작업이 동시에 실행되기를 원하며 한 번 알고 싶습니다. 그들은 모두 완료되었습니다.

이와 같은 시나리오의 가장 간단한 구현은 무엇입니까?

답변:


440

두 답변 모두 기다릴 수있는 언급하지 않았습니다 Task.WhenAll.

var task1 = DoWorkAsync();
var task2 = DoMoreWorkAsync();

await Task.WhenAll(task1, task2);

주요 차이점 Task.WaitAllTask.WhenAll전 의지 블록 (하여 유사한이다 Wait후자 동안 단일 작업에서)이 모든 작업이 완료 될 때까지 호출자에게 제어를 다시 산출 기다리게 될 수 없다.

따라서 예외 처리는 다릅니다.

Task.WaitAll:

하나 이상의 Task 인스턴스가 취소되었거나 하나 이상의 Task 인스턴스를 실행하는 동안 예외가 발생했습니다. 작업이 취소 된 경우 AggregateException에는 InnerExceptions 컬렉션에 OperationCanceledException이 포함됩니다.

Task.WhenAll:

제공된 작업 중 하나가 오류 상태에서 완료되면 반환 된 작업도 오류 상태에서 완료됩니다. 여기에는 예외에 제공된 각 작업의 래핑되지 않은 예외 집합이 포함됩니다.

제공된 작업 중 하나라도 오류가 발생했지만 그 중 하나 이상이 취소 된 경우 반환 된 작업은 취소됨 상태로 종료됩니다.

오류가 발생한 작업이없고 취소 된 작업이 없으면 결과 작업은 RanToCompletion 상태로 종료됩니다. 제공된 배열 / 열거 가능한 작업이없는 경우 반환 된 작업은 호출자에게 반환되기 전에 즉시 RanToCompletion 상태로 전환됩니다.


4
이 작업을 시도하면 작업이 순차적으로 실행됩니까? 전에 각 작업을 개별적으로 시작해야 await Task.WhenAll(task1, task2);합니까?
Zapnologica

4
@Zapnologica Task.WhenAll는 당신을 위해 작업을 시작하지 않습니다. "핫"을 제공해야합니다. 이미 시작했음을 의미합니다.
Yuval Itzchakov

2
확인. 말이 되네요 그렇다면 당신의 모범은 무엇을 할 것입니까? 당신이 그들을 시작하지 않았기 때문에?
Zapnologica

2
@YuvalItzchakov 감사합니다! 너무 간단하지만 오늘 많은 도움이되었습니다! 최소한 +1000의 가치가 있습니다 :)
Daniel Dušek

1
@Pierre 님을 팔로우하지 않습니다 StartNew비동기 적으로 대기하는 모든 작업과 회전하는 새로운 작업은 무엇 과 관련이 있습니까?
Yuval Itzchakov

106

다음과 같은 많은 작업을 만들 수 있습니다.

List<Task> TaskList = new List<Task>();
foreach(...)
{
   var LastTask = new Task(SomeFunction);
   LastTask.Start();
   TaskList.Add(LastTask);
}

Task.WaitAll(TaskList.ToArray());

48
나는 WhenAll을 추천 할 것입니다
Ravi

.Start () 대신 await 키워드를 사용하여 여러 개의 새 스레드를 동시에 시작할 수 있습니까?
Matt W

1
@MattW 아니요, await를 사용하면 완료 될 때까지 기다립니다. 이 경우 다중 스레드 환경을 만들 수 없습니다. 이것이 모든 작업이 루프의 끝에서 대기하는 이유입니다.
바이러스

5
이것이 차단 호출인지 확실하지 않기 때문에 미래 독자를위한 공감대.
JRoughan

그렇지 않은 이유에 대해서는 허용 된 답변을 참조하십시오.
EL MOJO

26

내가 본 가장 좋은 옵션은 다음 확장 방법입니다.

public static Task ForEachAsync<T>(this IEnumerable<T> sequence, Func<T, Task> action) {
    return Task.WhenAll(sequence.Select(action));
}

다음과 같이 호출하십시오.

await sequence.ForEachAsync(item => item.SomethingAsync(blah));

또는 비동기 람다로 :

await sequence.ForEachAsync(async item => {
    var more = await GetMoreAsync(item);
    await more.FrobbleAsync();
});

26

당신은 사용할 수 있습니다 WhenAllawaitable 반환하는 Task또는 WaitAll더 리턴 유형이없는과에 더 코드 실행 시뮬을 차단합니다 Thread.Sleep모든 작업이 완료 취소 또는 폴트 때까지.

여기에 이미지 설명을 입력하십시오

var tasks = new Task[] {
    TaskOperationOne(),
    TaskOperationTwo()
};

Task.WaitAll(tasks);
// or
await Task.WhenAll(tasks);

당신이 praticular 순서로 작업을 실행하려는 경우 양식 영감을 얻을 수있는 anwser를.


죄송합니다 늦게 파티에와 주셔서하지만, 왜해야합니까 await각 작업과 동시에 사용에 WaitAllWhenAll. Task[]초기화 작업이 없어야 await합니까?
dee zg

@dee zg 당신이 맞아요. 위의 기다림은 목적을 무너 뜨립니다. 대답을 바꾸고 제거하겠습니다.
NtFreX

그래 그거야. 설명 주셔서 감사합니다! (좋은 답변
dee zg

8

Tasks 를 체인으로 연결 하시겠습니까 , 아니면 병렬로 호출 할 수 있습니까?

체인의 경우
그냥 그런 짓을

Task.Run(...).ContinueWith(...).ContinueWith(...).ContinueWith(...);
Task.Factory.StartNew(...).ContinueWith(...).ContinueWith(...).ContinueWith(...);

결함이있을 수 있으므로 Task각각 의 이전 인스턴스 를 확인하는 것을 잊지 마십시오 ContinueWith.

병렬 방식으로
가장 간단한 방법은 다음과 같습니다. Parallel.Invoke 그렇지 않으면 s를 Task.WaitAll사용 WaitHandle하여 0 동작 남은 횟수에 카운트 다운을 수행 할 수도 있습니다 (대기, 새 클래스가 있습니다 CountdownEvent) 또는 ...


3
답변을 감사하십시오. 그러나 귀하의 제안은 조금 더 설명되었을 수 있습니다.
Daniel Minnaar

@ drminnaar 예제와 함께 msdn 링크 옆에 다른 설명이 필요합니까? 링크를 클릭조차하지 않았습니까?
Andreas Niedermair

4
링크를 클릭하고 내용을 읽었습니다. Invoke를 사용하려고했지만 비동기식으로 실행되는지 여부에 대한 많은 If와 But가있었습니다. 답을 계속 편집하고있었습니다. 게시 한 WaitAll 링크는 완벽했지만 동일한 기능을 더 빠르고 쉽게 읽을 수있는 방법으로 설명했습니다. 화를 내지 마십시오. 귀하의 답변은 여전히 ​​다른 접근법에 대한 훌륭한 대안을 제공합니다.
Daniel Minnaar

@drminnaar 여기에 아무 공격도, 난 그냥 궁금해 :)
Andreas Niedermair

5

이것이 Func <> 배열로 수행하는 방법입니다 .

var tasks = new Func<Task>[]
{
   () => myAsyncWork1(),
   () => myAsyncWork2(),
   () => myAsyncWork3()
};

await Task.WhenAll(tasks.Select(task => task()).ToArray()); //Async    
Task.WaitAll(tasks.Select(task => task()).ToArray()); //Or use WaitAll for Sync

1
왜 그것을 작업 배열로 유지하지 않습니까?
Talha Talip Açıkgöz

1
@ talha-talip-açıkgöz를주의하지 않으면 작업이 실행되지 않을 때 작업을 실행하십시오. Func 대리자로하는 것은 당신의 의도를 분명히합니다.
DalSoft

5

또 다른 대답 ...하지만 일반적으로 데이터를 동시에로드하고 변수와 같은 변수에 넣어야하는 경우가 있습니다.

var cats = new List<Cat>();
var dog = new Dog();

var loadDataTasks = new Task[]
{
    Task.Run(async () => cats = await LoadCatsAsync()),
    Task.Run(async () => dog = await LoadDogAsync())
};

try
{
    await Task.WhenAll(loadDataTasks);
}
catch (Exception ex)
{
    // handle exception
}

1
경우 LoadCatsAsync()LoadDogAsync()단지 데이터베이스 호출입니다 그들은 IO 바인딩입니다. Task.Run()CPU 바운드 작업을위한 것입니다. 데이터베이스 서버의 응답을 기다리는 것만으로 불필요한 오버 헤드가 추가됩니다. Yuval의 승인 된 답변은 IO 바운드 작업에 적합한 방법입니다.
Stephen Kennedy

@StephenKennedy 어떤 종류의 오버 헤드와 성능에 어느 정도 영향을 줄 수 있는지 명확하게 설명해 주시겠습니까? 감사!
Yehor Hromadskyi

주석 상자에 요약하기가 매우 어렵습니다. :) 대신 Stephen Cleary의 기사를 읽는 것이 좋습니다. 그는이 분야의 전문가입니다. 여기에서 시작하십시오 : blog.stephencleary.com/2013/10/…
Stephen Kennedy

-1

이 시나리오 중 일부에서 작업을 사용하는 방법을 보여주는 코드를 준비했습니다.

    // method to run tasks in a parallel 
    public async Task RunMultipleTaskParallel(Task[] tasks) {

        await Task.WhenAll(tasks);
    }
    // methode to run task one by one 
    public async Task RunMultipleTaskOneByOne(Task[] tasks)
    {
        for (int i = 0; i < tasks.Length - 1; i++)
            await tasks[i];
    }
    // method to run i task in parallel 
    public async Task RunMultipleTaskParallel(Task[] tasks, int i)
    {
        var countTask = tasks.Length;
        var remainTasks = 0;
        do
        {
            int toTake = (countTask < i) ? countTask : i;
            var limitedTasks = tasks.Skip(remainTasks)
                                    .Take(toTake);
            remainTasks += toTake;
            await RunMultipleTaskParallel(limitedTasks.ToArray());
        } while (remainTasks < countTask);
    }

1
작업 결과는 어떻게 얻습니까? 예를 들어, 데이터 테이블에서 병합 "행"(N 작업에서 병렬로)을 그리드 뷰 asp.net?
PreguntonCojoneroCabrón
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.