루프에서 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.