루프에서 await를 사용하는 방법


87

컬렉션에서 일부 작업을 수행하는 비동기 콘솔 앱을 만들려고합니다. async / await를 사용하는 다른 버전의 루프를 병렬로 사용하는 버전이 있습니다. 비동기 / 대기 버전이 병렬 버전과 유사하게 작동 할 것으로 예상했지만 동 기적으로 실행됩니다. 내가 도대체 ​​뭘 잘못하고있는 겁니까?

class Program
{
    static void Main(string[] args)
    {
        var worker = new Worker();
        worker.ParallelInit();
        var t = worker.Init();
        t.Wait();
        Console.ReadKey();
    }
}

public class Worker
{
    public async Task<bool> Init()
    {
        var series = Enumerable.Range(1, 5).ToList();
        foreach (var i in series)
        {
            Console.WriteLine("Starting Process {0}", i);
            var result = await DoWorkAsync(i);
            if (result)
            {
                Console.WriteLine("Ending Process {0}", i);
            }
        }

        return true;
    }

    public async Task<bool> DoWorkAsync(int i)
    {
        Console.WriteLine("working..{0}", i);
        await Task.Delay(1000);
        return true;
    }

    public bool ParallelInit()
    {
        var series = Enumerable.Range(1, 5).ToList();
        Parallel.ForEach(series, i =>
        {
            Console.WriteLine("Starting Process {0}", i);
            DoWorkAsync(i);
            Console.WriteLine("Ending Process {0}", i);
        });
        return true;
    }
}

답변:


126

await키워드를 사용하는 방식 은 병렬이 아닌 루프를 통과 할 때마다 기다리는 것을 C #에 알려줍니다. Tasks 목록을 저장 한 다음 await모두를 사용하여 원하는 작업을 수행하도록 메서드를 다시 작성할 수 있습니다 Task.WhenAll.

public async Task<bool> Init()
{
    var series = Enumerable.Range(1, 5).ToList();
    var tasks = new List<Task<Tuple<int, bool>>>();
    foreach (var i in series)
    {
        Console.WriteLine("Starting Process {0}", i);
        tasks.Add(DoWorkAsync(i));
    }
    foreach (var task in await Task.WhenAll(tasks))
    {
        if (task.Item2)
        {
            Console.WriteLine("Ending Process {0}", task.Item1);
        }
    }
    return true;
}

public async Task<Tuple<int, bool>> DoWorkAsync(int i)
{
    Console.WriteLine("working..{0}", i);
    await Task.Delay(1000);
    return Tuple.Create(i, true);
}

3
나는 다른 사람들에 대해 모르지만 병렬 for / foreach는 병렬 루프에 대해 더 간단 해 보입니다.
Brettski 2014

8
Ending Process알림 표시되는 시점은 작업이 실제로 종료되는 시점 이 아니라는 점에 유의하십시오 . 이러한 모든 알림은 마지막 작업이 완료된 직후 순차적으로 덤프됩니다 . "프로세스 1 종료"가 표시되면 프로세스 1이 오랫동안 종료되었을 수 있습니다. 거기에서 단어 선택 외에 +1.
Asad Saeeduddin 2014

@Brettski 내가 틀렸을 수 있지만 병렬 루프는 모든 종류의 비동기 결과를 트랩합니다. Task <T>를 반환하면 작업을 취소하거나 예외를 보는 등 내부에서 진행중인 작업을 관리 할 수있는 Task 개체를 즉시 반환합니다. 이제 Async / Await를 사용하면 좀 더 친숙한 방식으로 Task 개체를 사용할 수 있습니다. 즉, Task.Result를 수행 할 필요가 없습니다.
The Muffin Man

@Tim S, Tasks.WhenAll 메서드를 사용하여 비동기 함수로 값을 반환하려면 어떻게해야합니까?
Mihir

최대 실행 작업을 제한 하기 위해 Semaphorein DoWorkAsync을 구현하는 것이 나쁜 습관 입니까?
C4d

39

코드는 await다음 반복을 시작하기 전에 각 작업 (사용 )이 완료 될 때까지 기다립니다 .
따라서 병렬 처리가 없습니다.

기존 비동기 작업을 병렬로 실행하려면 필요하지 않습니다 await. Tasks 모음을 가져 와서 Task.WhenAll()모두를 기다리는 작업을 반환하기 위해 호출 하면됩니다.

return Task.WhenAll(list.Select(DoWorkAsync));

그래서 당신은 어떤 루프에서 어떤 비동기 메서드도 사용할 수 없습니까?
사티

4
@Satish : 할 수 있습니다. 그러나, await당신이 원하는 것과 정반대의 행동을합니다 – 그것은 완료 될 때 Task까지 기다립니다 .
SLaks

나는 당신의 대답을 받아들이고 싶었지만 Tims S가 더 나은 대답을 가지고 있습니다.
Satish

또는 작업 당신은 단지 그들을 위해 기다리고없이 메서드를 호출 할 수 있습니다 완료되면 당신은 알 필요가없는 경우
disklosr

구문이 무엇을하는지 확인하기 위해- DoWorkAsync각 항목에 대해 호출 된 Task를 실행하고 있습니까? list( 각 항목을에 전달 DoWorkAsync하며 단일 매개 변수가 있다고 가정합니다)?
jbyrd

12
public async Task<bool> Init()
{
    var series = Enumerable.Range(1, 5);
    Task.WhenAll(series.Select(i => DoWorkAsync(i)));
    return true;
}

4

C # 7.0 에서는 튜플의 각 멤버에 의미 체계 이름을 사용할 수 있습니다 . 여기 에 새 구문을 사용한 Tim S. 의 대답이 있습니다.

public async Task<bool> Init()
{
    var series = Enumerable.Range(1, 5).ToList();
    var tasks = new List<Task<(int Index, bool IsDone)>>();

    foreach (var i in series)
    {
        Console.WriteLine("Starting Process {0}", i);
        tasks.Add(DoWorkAsync(i));
    }

    foreach (var task in await Task.WhenAll(tasks))
    {
        if (task.IsDone)
        {
            Console.WriteLine("Ending Process {0}", task.Index);
        }
    }

    return true;
}

public async Task<(int Index, bool IsDone)> DoWorkAsync(int i)
{
    Console.WriteLine("working..{0}", i);
    await Task.Delay(1000);
    return (i, true);
}

task. 내부를foreach 제거 할 수도 있습니다 .

// ...
foreach (var (IsDone, Index) in await Task.WhenAll(tasks))
{
    if (IsDone)
    {
        Console.WriteLine("Ending Process {0}", Index);
    }
}
// ...
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.