Microsoft MVP성태의 닷넷 이야기
for문 안에 await가 있는 경우 질문드립니다. [링크 복사], [링크+제목 복사],
조회: 10669
글쓴 사람
한예지 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) 넵, 맞습니다.
정성태

... 61  62  63  64  65  66  67  68  [69]  70  71  72  73  74  75  ...
NoWriterDateCnt.TitleFile(s)
852임동찬1/6/201015441base.OnStart(agrs) [1]
851한귀순1/6/201020902oracle & transaction 질문 [5]
850박한주12/31/200918089COM과 C#간 권한 문제에 관해 여쭤봅니다. [5]
848날쌘돌이12/22/200918953C#으로 개발하는 ActiveX 디버깅 문제 [5]
847최승문12/16/200917836winform에서 웹서비스를 이용할때, 객체가 전송안되는 문제... [2]
846이강구12/14/200916775ATL COM 제작 문의 [3]
845김재영12/9/200919607ClickOnce에서 WinForm을 배포시 변동되는 Args값을 넘길 수 있나요? [3]파일 다운로드1
843김재영12/2/200917123Hyper-V 사용에 대해여 질문이 있습니다. [2]
842한귀순12/1/200919963Attempted to read or write protected memory [2]
841장근배11/30/200921547첨부 파일 Download시 한글 Name [2]
844장근배12/3/200916108    답변글 [답변]: 첨부 파일 Download시 한글 Name파일 다운로드1
840박영민11/20/200915940웹 애플리케이션 질문입니다. [1]
837left...10/21/200916807아래 게시글 관련하여 추가 질문 [3]
836left...10/19/200917807ActiveX 상에서 암시적 dll 로드 [1]
832김우진10/15/200914498C# 에서 swa + security 구현 방법이 없을까요?
833정성태10/15/200918587    답변글 [답변]: C#에서 swa + security 구현 방법이 없을까요? [1]
834김우진10/15/200914608        답변글 예제 메시지 올려드릴께요. [2]파일 다운로드1
838이영우10/30/200914514            답변글 국세청 전자세금계선서에 관하여 문의드립니다.
831채승수10/13/200916800스마트 클라이언트 GAC 등록 문제 여쭤봅니다... [2]
829박찬용10/9/200929288WebBrowser 컨트롤 사용시 새창을 띄울시 세션/쿠키 공유가 왜 안될까요?? [1]파일 다운로드1
827송창훈9/24/200914325[질문]CAS 설정
828정성태9/28/200917671    답변글 [답변]: [질문]CAS 설정 [1]
825김재영9/18/200915253프레임워크별 비트 지원에 대해서 문의를 드립니다. [2]
821채승수8/27/200914713클릭원스를 비스타에서 실행시 [1]
822채승수8/28/200919612    답변글 [답변]: 클릭원스를 비스타에서 실행시 [2]
818개발돌이8/14/200917261ActiveX개발한 dll을 임베디드 할때 UI에 대한 질문 [1]파일 다운로드1
... 61  62  63  64  65  66  67  68  [69]  70  71  72  73  74  75  ...