Microsoft MVP성태의 닷넷 이야기
for문 안에 await가 있는 경우 질문드립니다. [링크 복사], [링크+제목 복사],
조회: 4360
글쓴 사람
한예지 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)
4966포플러3/30/20189708    답변글 [답변]: C# 응용프로그램 (Winform)에서 unhandledexception 발생시 프로그램이 죽는 현상 이외에 재부팅될 수도 있을까요? [1]
4961김민욱3/26/201810633레이더 뷰어의 구현 방법(이미지 확대 축소 관련) [2]
4960hurd...3/18/201810372OCX 관련한 질문을 드리고자 합니다. [1]
4959익명3/10/20189436교재 199page 델리게이트와 object를 이용한 범용 정렬 코드 [1]
4957멍멍이2/13/201810048System.Console - WriteLine함수의 제너릭 사용 [1]
4956김성대2/12/20189376asp.net 질문입니다. [1]
4955웅이2/12/20189825[삭제] WPF에서 list 속도 향상하는 방법이 있을까요?
4954초보자2/8/201811658FFT Library 사용 [1]
4950ASP열공2/5/20189409asp.net 과 C# 을 이용해서 홈페이지 만드는 질문입니다. [1]
4951ASP열공2/6/201810398    답변글 [답변]: asp.net 과 C# 을 이용해서 홈페이지 만드는 질문입니다. [1]
4952ASP열공2/7/20189553        답변글 [답변]: [답변]: asp.net 과 C# 을 이용해서 홈페이지 만드는 질문입니다. [1]
4953ASP열공2/7/20189071            답변글 [답변]: [답변]: [답변]: asp.net 과 C# 을 이용해서 홈페이지 만드는 질문입니다.
4949김성대1/31/201810750비동기 질문입니다. [3]파일 다운로드1
494880511/31/20189542C# 7.1책 보다가 질문드립니다 [1]
4947김성대1/30/201810744[삭제] 비동기 질문입니다. [2]파일 다운로드2
4946윤현수1/29/201810293비동기 TCP통신 데이터 문제 [4]파일 다운로드1
4945김성대1/26/20188814비동기 질문입니다. [1]파일 다운로드1
4944popo1/25/20188243[삭제] 스레드 안에서 Window Visibility 변경시 DialogResult 오류 질문
4943김성대1/22/20189211FromAsync 질문입니다. [1]
4942박현일1/19/201810824WPF 공부중 모르는 문법이 있어서요~^^; [2]
4941김성대1/18/20189092비동기예약어 실행오류관련입니다. [1]
4940plzh...1/10/20189982webbrowser2 를 이용한 sns 로그인 구현 관련 문의 [2]
4939이성일1/4/201811477ClickOnce 배포 후 업데이트 시 발생하는 오류에 대해 질문 드립니다. [2]
4938김성대1/3/20189552채팅 프로그램관련 질문입니다. [9]파일 다운로드1
4937Ques...1/3/201811931C# CPU 사용량 한계치 늘리는 방법 [2]
4936무개1/3/20189076책 내용 인용하는 것에 관한 질문이있습니다. [1]파일 다운로드1
... 31  32  33  34  35  [36]  37  38  39  40  41  42  43  44  45  ...