Microsoft MVP성태의 닷넷 이야기
닷넷: 2147. C# - 비동기 메서드의 async 예약어 유무에 따른 차이 [링크 복사], [링크+제목 복사],
조회: 3684
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 1개 있습니다.)
(시리즈 글이 9개 있습니다.)
.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




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

비밀번호

댓글 작성자
 




... [46]  47  48  49  50  51  52  53  54  55  56  57  58  59  60  ...
NoWriterDateCnt.TitleFile(s)
12499정성태1/20/202110659.NET Framework: 1013. .NET Core Kestrel 호스팅 - 포트 변경, non-localhost 접속 지원 및 https 등의 설정 변경 [1]파일 다운로드1
12498정성태1/20/20219653.NET Framework: 1012. .NET Core Kestrel 호스팅 - 비주얼 스튜디오의 Kestrel/IIS Express 프로파일 설정
12497정성태1/20/202110606.NET Framework: 1011. C# - OWIN Web API 예제 프로젝트 [1]파일 다운로드2
12496정성태1/19/20219536.NET Framework: 1010. .NET Core 콘솔 프로젝트에서 Kestrel 호스팅 방법 [1]
12495정성태1/19/202111440웹: 40. IIS의 HTTP/2 지원 여부 - h2, h2c [1]
12494정성태1/19/202110749개발 환경 구성: 522. WSL2 인스턴스와 호스트 측의 Hyper-V에 운영 중인 VM과 네트워크 연결을 하는 방법 [2]
12493정성태1/18/20218972.NET Framework: 1009. .NET 5에서의 네트워크 라이브러리 개선 (1) - HTTP 관련 [1]파일 다운로드1
12492정성태1/17/20218401오류 유형: 695. ASP.NET 0x80131620 Failed to bind to address
12491정성태1/16/202110152.NET Framework: 1008. 배열을 반환하는 C# COM 개체의 메서드를 C++에서 사용 시 메모리 누수 현상 [1]파일 다운로드1
12490정성태1/15/20219652.NET Framework: 1007. C# - foreach에서 열거 변수의 타입을 var로 쓰면 object로 추론하는 문제 [1]파일 다운로드1
12489정성태1/13/202110625.NET Framework: 1006. C# - DB에 저장한 텍스트의 (이모티콘을 비롯해) 유니코드 문자가 '?'로 보인다면? [1]
12488정성태1/13/202110866.NET Framework: 1005. C# - string 타입은 shallow copy일까요? deep copy일까요? [2]파일 다운로드1
12487정성태1/13/20219281.NET Framework: 1004. C# - GC Heap에 위치한 참조 개체의 주소를 알아내는 방법파일 다운로드1
12486정성태1/12/202110254.NET Framework: 1003. x64 환경에서 참조형의 기본 메모리 소비는 얼마나 될까요? [1]
12485정성태1/11/202110994Graphics: 38. C# - OpenCvSharp.VideoWriter에 BMP 파일을 1초씩 출력하는 예제파일 다운로드1
12484정성태1/9/202111659.NET Framework: 1002. C# - ReadOnlySequence<T> 소개파일 다운로드1
12483정성태1/8/20218712개발 환경 구성: 521. dotPeek - 훌륭한 역어셈블 소스 코드 생성 도구
12482정성태1/8/202110292.NET Framework: 1001. C# - 제네릭 타입/메서드에서 사용 시 경우에 따라 CS8377 컴파일 에러
12481정성태1/7/20219940.NET Framework: 1000. C# - CS8344 컴파일 에러: ref struct 타입의 사용 제한 메서드파일 다운로드1
12480정성태1/6/202112588.NET Framework: 999. C# - ArrayPool<T>와 MemoryPool<T> 소개파일 다운로드1
12479정성태1/6/20219998.NET Framework: 998. C# - OWIN 예제 프로젝트 만들기
12478정성태1/5/202111636.NET Framework: 997. C# - ArrayPool<T> 소개파일 다운로드1
12477정성태1/5/202113947기타: 79. github 코드 검색 방법 [1]
12476정성태1/5/202110693.NET Framework: 996. C# - 닷넷 코어에서 다른 스레드의 callstack을 구하는 방법파일 다운로드1
12475정성태1/5/202113283.NET Framework: 995. C# - Span<T>와 Memory<T> [1]파일 다운로드1
12474정성태1/4/202110721.NET Framework: 994. C# - (.NET Core 2.2부터 가능한) 프로세스 내부에서 CLR ETW 이벤트 수신 [1]파일 다운로드1
... [46]  47  48  49  50  51  52  53  54  55  56  57  58  59  60  ...