Microsoft MVP성태의 닷넷 이야기
닷넷: 2147. C# - 비동기 메서드의 async 예약어 유무에 따른 차이 [링크 복사], [링크+제목 복사],
조회: 11001
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 1개 있습니다.)
(시리즈 글이 12개 있습니다.)
.NET Framework: 698. C# 컴파일러 대신 직접 구현하는 비동기(async/await) 코드
; https://www.sysnet.pe.kr/2/0/11351

.NET Framework: 716. async 메서드의 void 반환 타입 사용에 대하여
; https://www.sysnet.pe.kr/2/0/11414

.NET Framework: 717. Task를 포함하지 않는 async 메서드의 동작 방식
; https://www.sysnet.pe.kr/2/0/11415

.NET Framework: 719. Task를 포함하는 async 메서드의 동작 방식
; https://www.sysnet.pe.kr/2/0/11417

.NET Framework: 731. C# - await을 Task 타입이 아닌 사용자 정의 타입에 적용하는 방법
; https://www.sysnet.pe.kr/2/0/11456

.NET Framework: 737. C# - async를 Task 타입이 아닌 사용자 정의 타입에 적용하는 방법
; https://www.sysnet.pe.kr/2/0/11484

.NET Framework: 813. C# async 메서드에서 out/ref/in 유형의 인자를 사용하지 못하는 이유
; https://www.sysnet.pe.kr/2/0/11850

닷넷: 2138. C# - async 메서드 호출 원칙
; https://www.sysnet.pe.kr/2/0/13405

닷넷: 2147. C# - 비동기 메서드의 async 예약어 유무에 따른 차이
; https://www.sysnet.pe.kr/2/0/13421

닷넷: 2318. C# - (async Task가 아닌) async void 사용 시의 부작용
; https://www.sysnet.pe.kr/2/0/13884

닷넷: 2319. ASP.NET Core Web API / Razor 페이지에서 발생할 수 있는 async void 메서드의 부작용
; https://www.sysnet.pe.kr/2/0/13885

닷넷: 2321. Blazor에서 발생할 수 있는 async void 메서드의 부작용
; https://www.sysnet.pe.kr/2/0/13888




C# - 비동기 메서드의 async 예약어 유무에 따른 차이

현실적으로 await 예약어를 사용해 호출할 수 있는 대상 메서드는 Task를 반환해야 합니다. 단지, 상황에 따라 async 예약어를 곁들일 수 있는데요, 아래의 예제 코드를 보면,

namespace ConsoleApp1;

internal class Program
{
    static async Task Main(string[] args)
    {
        DateTime now = DateTime.Now;
        await DelayTask();
        await DelayAsync();
        Console.WriteLine($"{(DateTime.Now - now).TotalSeconds}"); // 4초 소요
    }

    static Task DelayTask()
    {
        return Task.Delay(1000);
    }

    static async Task DelayAsync()
    {
        await Task.Delay(3000);
    }
}

결국 await 호출을 하는 측의 입장에서 보면 지정한 시간 동안 대기하는 동작은 똑같지만, DelayTask와 DelayAsync 메서드의 내부 코드에는 살짝(?) 다른 점이 존재합니다. 실제로 위의 예제 코드는 C# 컴파일러에 의해 각각 아래와 같이 처리됩니다.

private static Task DelayTask() // 변화가 없지만,
{
    return Task.Delay(2000);
}

[DebuggerStepThrough]
private static Task DelayAsync() // async 예약어로 인해 내부 코드를 상태 머신을 활용하는 코드로 변경
{
    Program.<DelayAsync>d__2 <DelayAsync>d__ = new Program.<DelayAsync>d__2();
    <DelayAsync>d__.<>t__builder = AsyncTaskMethodBuilder.Create();
    <DelayAsync>d__.<>1__state = -1;
    <DelayAsync>d__.<>t__builder.Start<Program.<DelayAsync>d__2>(ref <DelayAsync>d__);
    return <DelayAsync>d__.<>t__builder.Task;
}

게다가 DelayAsync의 경우 위의 코드뿐만 아니라 내부 코드를 분할해 실행하는 상태 머신을 구현한 클래스를 별도로 하나 더 생성하기 때문에,

[CompilerGenerated]
private sealed class <DelayAsync>d__2 : IAsyncStateMachine
{
    // ...[생략]...

    void IAsyncStateMachine.MoveNext()
    {
        // ...[생략]...
    }

    [DebuggerHidden]
    void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
    {
    }
}

복잡도가 더 증가합니다. 단순히 생각해 봐도, 어차피 같은 역할을 하는 것이라면 async를 적용하지 않는 편이 더 깔끔하고 성능상으로도 (작게나마) 이득을 볼 수 있습니다. 그렇다면 async를 붙이는 적절한 기준이 있을까요?

간단합니다. ^^ 내부 코드에서 await을 써야 한다면 async를 붙이면 되고, 없으면 떼는 것을 권장합니다. 하지만 이런 규칙을 굳이 염두에 두지 않아도 되는 것은, 애당초 Visual Studio 코드 창에서 편집하게 되면 await이 없는 async 메서드에 대해서는 CS1998 경고를 발생하기 때문입니다.

warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.


그래도 저 규칙을 인지하고 있으면, 위와 같은 사례에서 볼 수 있듯이 굳이 DelayAsync와 같이 코딩하는 상황을 DelayTask와 같이 (능동적으로) 바꿔 쓸 수 있을 것입니다. 참고로, 아래의 글에서도,

Long Story Short: Async/Await Best Practices in .NET
; https://medium.com/@deep_blue_day/long-story-short-async-await-best-practices-in-net-1f39d7d84050

"Consider using return Task instead of return await"라는 소제목으로 이에 대한 설명을 하고 있습니다.

그렇긴 한데, 다른 글에서 설명하겠지만 단순히 Task를 반환하는 것은 약간의 혼란이 발생할 수 있는 여지가 있어서 좀 아쉬운 점이 있습니다.




await 호출의 대상이 되는 메서드를 awaitable하다고 하며, 이 조건을 만족하기 위해서는 GetAwaiter 메서드를 가진 타입이기만 하면 됩니다. 일례로 우리가 자주 접하게 되는 Task 타입이 그러한 대표적인 사례입니다. 이후 마이크로소프트가 GC를 괴롭히지 않기 위한 보조 타입으로 만든 ValueTask 역시 GetAwaiter 메서드를 가지고 있습니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 10/18/2023]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




1  2  3  [4]  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13843정성태12/13/20244381오류 유형: 938. Docker container 내에서 빌드 시 error MSB3021: Unable to copy file "..." to "...". Access to the path '...' is denied.
13842정성태12/12/20244523디버깅 기술: 205. Windbg - KPCR, KPRCB
13841정성태12/11/20244855오류 유형: 937. error MSB4044: The "ValidateValidArchitecture" task was not given a value for the required parameter "RemoteTarget"
13840정성태12/11/20244428오류 유형: 936. msbuild - Your project file doesn't list 'win' as a "RuntimeIdentifier"
13839정성태12/11/20244857오류 유형: 936. msbuild - error CS1617: Invalid option '12.0' for /langversion. Use '/langversion:?' to list supported values.
13838정성태12/4/20244595오류 유형: 935. Windbg - Breakpoint 0's offset expression evaluation failed.
13837정성태12/3/20245052디버깅 기술: 204. Windbg - 윈도우 핸들 테이블 (3) - Windows 10 이상인 경우
13836정성태12/3/20244623디버깅 기술: 203. Windbg - x64 가상 주소를 물리 주소로 변환 (페이지 크기가 2MB인 경우)
13835정성태12/2/20245055오류 유형: 934. Azure - rm: cannot remove '...': Directory not empty
13834정성태11/29/20245285Windows: 275. C# - CUI 애플리케이션과 Console 윈도우 (Windows 10 미만의 Classic Console 모드인 경우) [1]파일 다운로드1
13833정성태11/29/20244965개발 환경 구성: 737. Azure Web App에서 Scale-out으로 늘어난 리눅스 인스턴스에 SSH 접속하는 방법
13832정성태11/27/20244901Windows: 274. Windows 7부터 도입한 conhost.exe
13831정성태11/27/20244381Linux: 111. eBPF - BPF_MAP_TYPE_PERF_EVENT_ARRAY, BPF_MAP_TYPE_RINGBUF에 대한 다양한 용어들
13830정성태11/25/20245184개발 환경 구성: 736. 파이썬 웹 앱을 Azure App Service에 배포하기
13829정성태11/25/20245153스크립트: 67. 파이썬 - Windows 버전에서 함께 설치되는 py.exe
13828정성태11/25/20244439개발 환경 구성: 735. Azure - 압축 파일을 이용한 web app 배포 시 디렉터리 구분이 안 되는 문제파일 다운로드1
13827정성태11/25/20245091Windows: 273. Windows 환경의 파일 압축 방법 (tar, Compress-Archive)
13826정성태11/21/20245320닷넷: 2313. C# - (비밀번호 등의) Console로부터 입력받을 때 문자열 출력 숨기기(echo 끄기)파일 다운로드1
13825정성태11/21/20245652Linux: 110. eBPF / bpf2go - BPF_RINGBUF_OUTPUT / BPF_MAP_TYPE_RINGBUF 사용법
13824정성태11/20/20244750Linux: 109. eBPF / bpf2go - BPF_PERF_OUTPUT / BPF_MAP_TYPE_PERF_EVENT_ARRAY 사용법
13823정성태11/20/20245288개발 환경 구성: 734. Ubuntu에 docker, kubernetes (k3s) 설치
13822정성태11/20/20245151개발 환경 구성: 733. Windbg - VirtualBox VM의 커널 디버거 연결 시 COM 포트가 없는 경우
13821정성태11/18/20245079Linux: 108. Linux와 Windows의 프로세스/스레드 ID 관리 방식
13820정성태11/18/20245249VS.NET IDE: 195. Visual C++ - C# 프로젝트처럼 CopyToOutputDirectory 항목을 추가하는 방법
13819정성태11/15/20244477Linux: 107. eBPF - libbpf CO-RE의 CONFIG_DEBUG_INFO_BTF 빌드 여부에 대한 의존성
13818정성태11/15/20245267Windows: 272. Windows 11 24H2 - sudo 추가
1  2  3  [4]  5  6  7  8  9  10  11  12  13  14  15  ...