Microsoft MVP성태의 닷넷 이야기
for문 안에 await가 있는 경우 질문드립니다. [링크 복사], [링크+제목 복사],
조회: 4361
글쓴 사람
한예지 donator
홈페이지
첨부 파일
 

선생님 오늘 마지막 질문이 될 것 같습니다!

namespace Temp
{
    class Program
    {
        private static async void SimpleTest()
        {
            int sum = 0;
            for (int index = 1; index < 4; index++)
            {
                Console.WriteLine("Code Block ##{0}", index);
                sum += await ServerCallAsync();
            }
            Console.WriteLine("Result : {0}", sum);
        }

        public static Task<int> ServerCallAsync()
        {
            return Task<int>.Run(() =>
            {
                return 5;
            });
        }

      
        static void Main(string[] args)
        {
            SimpleTest();
            Console.ReadLine();
        }
    }
}

Main Thread는 Code Block ##1 출력 후 SimpleTest() 메서드를 빠져나간다.

ServerCallAsync 메서드 내부 : 스레드 풀에서 새로운 스레드 3번을 꺼내서 5를 반환한다.
await 아래 문장들 실행 : 스레드 풀에서 새로운 스레드 4번을 꺼내서 아래 3문장을 실행한다.
sum = sum + 5;
index++; (i = 2)
Code Block ##2 출력

ServerCallAsync 메서드 내부 : 스레드 풀에서 스레드 5번을 꺼내서 5를 반환한다.
await 아래 문장들 실행 : 스레드 풀에서 새로운 스레드 6번을 꺼내서 아래 3문장을 실행한다.
sum = sum + 5;
index++; (i = 3)
Code Block ##3 출력

ServerCallAsync 메서드 내부 : 스레드 풀에서 새로운 스레드 7번을 5를 반환한다.
await 아래 문장들 실행 : 스레드 풀에서 새로운 스레드 8번을 꺼내서 아래 2문장을 실행한다.
sum = sum + 5;
Result : 15 출력

[질문 ①]
스레드 풀에 요청을 6번하는 코드 흐름이 맞나요?
정확한 이해를 위해서 스레드 풀에 호출할 때마다 매번 다른 스레드가 호출된다고 가정했습니다.

[질문 ②]
현재 제가 사용하는 컴퓨터는 코어가 2개입니다.
그래서 실제로 위에 코드를 실행시키면 스레드 3번, 4번 오직 2개만 생성해서 처리하더라구요.
이것은 CPU 코어 수만큼 Working 스레드를 만드는 것이
context switching 비용을 최소화하기 때문에
2개의 Working 스레드만 사용했다고 생각해도 되나요?








[최초 등록일: ]
[최종 수정일: 5/10/2022]


비밀번호

댓글 작성자
 



2022-05-10 10시34분
1)
await 아래 문장들 실행: 스레드 풀에서 새로운 스레드 4번을 꺼내서 아래 3문장을 실행한다.

==>

await 이후의 코드를 "스레드 풀에서 새로운 스레드"를 꺼내는 것은 I/O 호출에서 발생하는 현상입니다. 그 외의 경우에는 해당 I/O 스레드를 구동하려면 I/O 호출에서 했던 것과 같은 식의 코딩을 해야 하는데, 본문에서는 그런 경우가 아니기 때문에 그런 식의 "새로운 스레드"가 선택되는 작업은 없습니다.

위의 코드에서는 Thread.Run이 호출되고 있으며, 그 안에 전달한 코드를 실행하도록 스레드 풀의 스레드(A 스레드)를 선택하고 있는데요, 바로 그 스레드가 I/O 호출에서 선택된 스레드의 역할을 하게 됩니다. 즉, Thread.Run으로 구동된 A 스레드가 내부의 코드를 완료한 이후 await으로 분리된 코드를 실행해 가기 시작합니다.

그러다가, 다시 for 문에 의해 그 A 스레드는 Thread.Run을 호출하게 되고, 그 순간 다시 스레드 풀로부터 선택된 새로운 스레드(B 스레드)가 작업을 수행하게 됩니다. 그 순간 A 스레드는 실행할 코드가 더 이상 없으므로 스레드 풀로 반환이 됩니다.

이후는 위의 설명이 반복됩니다.

2)
위에서 보면 A 스레드가 최초 선택되고, B 스레드가 선택이 된 후 A 스레드는 풀에 반환이 됩니다. 이후 다시 B 스레드가 Task.Run을 수행하면 스레드 풀에 여유로 남게 된 A 스레드가 선택이 됩니다. 그것이 반복되기 때문에 2개의 스레드로 처리가 되는 것입니다.
정성태
2022-05-11 02시29분
[한예지] [질문 ①] 선생님 답변에 근거하면 결국 위의 코드는 스레드 풀에 스레드 요청을 3번 한 것이 맞을까요?
        (for문 안에서 ServerCallAsync() 호출한 코드를 의미)

위의 코드를 약간 수정했습니다.
ServerCallAsync → NetworkStream.ReadAsync
int sum → int received
namespace Temp
{
    class Program
    {
        private static async void SimpleTest()
        {
            int received = 0;
            for (int index = 1; index < 4; index++)
            {
                Console.WriteLine("Code Block ##{0}", index);
                received += await ns.ReadAsync(...); // ns는 NetworkStream의 객체
            }
            Console.WriteLine("Result : {0}", received);
        }
      
        static void Main(string[] args)
        {
            SimpleTest();
            Console.ReadLine();
        }
    }
}

Main Thread는 Code Block ##1 출력 후
IRP(I/O 요청 패킷)을 디바이스 드라이버의 IRP 큐에
IRP를 큐잉 작업까지 처리하고 SimpleTest() 메서드를 빠져나간다.

스레드 풀에서 새로운 스레드 A을 꺼내서 await 아래 3문장을 실행한다.
received = received + 5;
index++; (i = 2)
Code Block ##2 출력

스레드 A는 IRP(I/O 요청 패킷)을 디바이스 드라이버의 IRP 큐에
IRP를 큐잉 작업까지 처리한다.
스레드 풀에서 새로운 스레드 B를 꺼내서 await 아래 3문장을 실행한다.
received = received + 5;
index++; (i = 3)
Code Block ##3 출력

스레드 B는 IRP(I/O 요청 패킷)을 디바이스 드라이버의 IRP 큐에
IRP를 큐잉 작업까지 처리한다.
스레드 풀에서 새로운 스레드 C를 꺼내서 await 아래 2문장을 실행한다.
received = received + 5;
Result : 15 출력

[질문 ②]
코드 흐름이 맞다면 for문 안에서 NetworkStream.ReadAsync를 호출하면
스레드 풀에 요청을 3번하는 코드 흐름이 맞나요?
정확한 이해를 위해서 스레드 풀에 호출할 때마다 매번 다른 스레드가 호출된다고 가정했습니다.

[질문 ③]
// [case ①] await 이후 코드는 스레드 풀에서 새로운 쓰레드를 꺼낸다.
int received = await I/O 호출 메서드();
Console.WriteLine(received);

// [case ②] await 이후 코드는 MyAsync를 수행한 스레드가 실행한다.
int received = await MyAsync();
Console.WriteLine(received);

참고로 MyAsync 메서드 코드는 아래와 같습니다.
public static Task<int> MyAsync()
{
    return Task<int>.Run(() =>
    {
        return 5;
    });
}

결국 await 키워드 뒤에 I/O 호출 메서드가 아니라
MyAsync 메서드처럼 Task<int>를 반환하는 메서드라면
await 이후 코드가 동기적으로 실행된다고 보는 것이 맞을까요?
그러니까 Task.Run으로 구동된 스레드가 코드를 완료한 후 await으로 분리된 코드를 실행한다고 말씀하셨기 때문에
동기적으로 실행한다고 이해했는데 맞을까요?

[질문 ③] 당연히 I/O 호출 발생하는 메서드는 아래와 같은 것이겠죠?
FileStream.ReadAsync
WebClient.DownloadStringAsync,
NetworkStream.ReadAsync
NetworkStream.WriteAsync
StreamReader.ReadLineAsync
StreamWriter.WriteAsync
[guest]
2022-05-11 10시02분
1) 맞습니다. 약간 복잡한 부분을 추가해 보면, 경우에 따라 I/O 메서드는 비동기 호출이 아닌, 곧바로 제어를 반환할 때도 있습니다. 가령, Disk에서 파일을 읽어들일 때, 1바이트를 ReadAsync해도 내부적으로는 (일반적으로) 4K씩 디스크 읽기가 발생하므로 연이은 ReadAsync의 경우에는 파일 cache에 담긴 내용을 곧바로 읽어들이므로 제어를 바로 반환합니다. 그런 경우에는 스레드 풀의 스레드가 관여하지 않고, ReadAsync를 호출한 스레드가 await 이후 작업도 담당합니다.

2) 맞습니다. 그런데 "스레드 풀에 요청을 3번"한다는 것이 이 글을 읽는 분들로 하여금 오해의 소지가 있는데요. ReadAsync를 호출하는 스레드가 직접 "스레드 풀에 요청을" 하는 것은 아닙니다. I/O 동작이 완료된 후 CLR 내부의 코드에 의해 스레드 풀로부터 스레드가 하나가 불려와 await 이후의 요청을 처리하는 것입니다.

3)
case 1번의 경우, 1)번 질문과 같으므로 생략합니다.

case 2번의 경우, Task.Run으로 인해 CLR Thread Pool에서 선택된 스레드 하나가 "return 5" 코드를 수행하고 await 이후의 "Console.WriteLine(...)" 코드도 수행합니다. 따라서, Task.Run으로 빌려진 스레드 입장에서는 해당 코드를 동기적으로 수행한다고 봐야 하고, 애당초 MyAsync를 수행하던 스레드 입장에서는 이후의 과정이 (Task.Run에 의해) 비동기로 수행한다고 보면 됩니다. (사실, 여기서도 복잡한 경우가 하나 더 있는데, 이것은 나중에 별도의 글로 정리해 보겠습니다.)

4) 넵, 맞습니다.
정성태

... 31  32  33  34  35  36  37  38  39  40  41  [42]  43  44  45  ...
NoWriterDateCnt.TitleFile(s)
4801spow...2/3/201710513C#의 참조 동작관련하여 아이디어 문의 드립니다 [4]
4800오세운2/1/201714598C# 에서 C++ DLL임포트 질문입니다. [2]
4798guest1/29/201711950picturebox의 image.dispose [1]
4797김철환1/13/201711899책에 관한 질문입니다 [3]
4796Bere...1/13/201711944++ 후위연산자와 = 을 함께 사용할 때 생성되는 IL 코드 관련... [2]
4795김철환1/11/201713137이벤트 부분을 읽고 있는데 이해가 안되서 질문합니다.. [11]
4794김철환1/10/201710429안녕하세요 c# 6.0 책을 구매한 사람인데요 [3]
4793장준영1/7/201712493안녕하세요 c언어 처음 공부해보는 학생입니다 [4]파일 다운로드1
4792김재영1/4/201713419소스코드 공개 전 성태님의 의견을 듣고싶습니다 [3]
4791C#초보12/28/201613355비동기 소켓 close시 ObjectDisposedException 문제점 질문 있습니다.. [1]
4790미나리12/24/201613593파워포인트 쇼 제어 SimpleHttpServer.cs 작동문제 [4]파일 다운로드1
4789김솔지12/21/201612413프린트 시, 프린트하는 파일의 파일명 구하는 부분에 대해서 질문드립니다. [1]
4788짜두12/19/201612141Visual Studio 2015 에서 msbuild 12 사용 [5]
4787guest12/18/201613924VLC라이브러리에 대해 아시나요? [3]파일 다운로드1
4785Hyou...12/16/201614042WPF 개발 시 MVVM 프레임워크 사용 [2]
4784ds12/15/201610688문의 드립니다. [2]
4783후배12/13/201612288MemoryStream에 관한 질문 입니다. [5]
4782김형민12/6/201610597[ C# 6.0 ] 126p 오타인가요? [6]
4781질문자11/29/201610736ms워드 저장 오류 [1]
4780최진11/28/201615186안녕 하세요 빌드 관련해서 질문드립니다 꾸벅 [4]
4779손니11/28/201611536안녕하세요 질문하다 드려도 될까요 [3]
4778김상호11/25/201611053재귀호출->비재귀호출 [2]파일 다운로드1
4777권오영11/12/201613311아래 질문 상세 소스전체입니다.. [3]
4776권오영11/11/201611212제가 이클립스를 공부중인데..이상한것을 찾았습니다.. [2]
4775이성환11/11/201614636안녕하세요. SnapsToDevicePixels 질문입니다. [5]파일 다운로드1
4774popo11/10/201611324.net SSL통신 관련 질문 드립니다. [1]
... 31  32  33  34  35  36  37  38  39  40  41  [42]  43  44  45  ...