성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# - Task에 전달한 Action, Func 유형에 따라 달라지는 async/await 비동기 처리</h1> <p> 아래의 글을 한번 볼까요? ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Beware of how you create a Task ; <a target='tab' href='https://dev.to/glsolaria/beware-of-how-you-create-a-task-23h9'>https://dev.to/glsolaria/beware-of-how-you-create-a-task-23h9</a> </pre> <br /> 글쓴이는 원래,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Going to sleep ... ... Woke up Run complete </pre> <br /> 위와 같은 출력을 기대하면서 다음과 같이 코딩을 했다고 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static async Task FireAndForget() { // Warning: Do not do this ... Task task = <span style='color: blue; font-weight: bold'>new Task</span>(<span style='color: blue; font-weight: bold'>async</span> () => { Console.WriteLine("Going to sleep ..."); <span style='color: blue; font-weight: bold'>await Task.Delay(1000);</span> Console.WriteLine("... Woke up"); }); task.Start(); <span style='color: blue; font-weight: bold'>await task;</span> } static async Task Main(string[] args) { <span style='color: blue; font-weight: bold'>await FireAndForget();</span> Console.WriteLine("Run complete"); Console.ReadKey(); } /* 출력 결과 Going to sleep ... Run complete ... Woke up */ </pre> <br /> 그러면서 저렇게 원래의 의도대로 실행시키기 위해 코드를 다음과 같이 수정했다고 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static async Task FireAndAwaitAsync() { var funcTask = <span style='color: blue; font-weight: bold'>new Func<Task></span>(<span style='color: blue; font-weight: bold'>async</span> () => { Console.WriteLine("Going to sleep ..."); await Task.Delay(1000); Console.WriteLine("... Woke up"); }); <span style='color: blue; font-weight: bold'>Task task = funcTask.Invoke(); await task;</span> } /* 원 글은 위의 코드를 사용했지만, 아래와 같이 async를 빼고 Task를 직접 반환해도 됩니다. static Task FireAndAwaitAsync() { var funcTask = <span style='color: blue; font-weight: bold'>new Func<Task></span>(<span style='color: blue; font-weight: bold'>async</span> () => { Console.WriteLine("Going to sleep ..."); <span style='color: blue; font-weight: bold'>await Task.Delay(1000);</span> Console.WriteLine("... Woke up"); }); <span style='color: blue; font-weight: bold'>return funcTask.Invoke();</span> } */ static async Task Main(string[] args) { await FireAndAwaitAsync(); Console.WriteLine("Run complete"); Console.ReadKey(); } /* 출력 결과 Going to sleep ... ... Woke up Run complete */ </pre> <br /> 글쓴이는, 마지막에 사실 자신이 만든 코드처럼 Task 생성과 실행을 분리할 필요가 없으므로 저런 식으로 이슈가 될 일이 없을 거라고 말합니다.<br /> <br /> <hr style='width: 50%' /><br /> <a name='run_async'></a> <br /> 글쓴이의 말대로, 아마도 대개의 경우 해당 코드를 다음과 같이 간단하게 구현할 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static <span style='color: blue; font-weight: bold'>Task</span> FireAndAwaitAsync2() { return <span style='color: blue; font-weight: bold'>Task.Run(async</span> () => { Console.WriteLine("Going to sleep ..."); await Task.Delay(1000); Console.WriteLine("... Woke up"); }); } </pre> <br /> 그런데, 저렇게 Task.Run을 하면 순차적으로 실행하는 반면, new Task로 실행하면 왜 병렬로 실행하는 것일까요?<br /> <br /> 이것은 Task.Run 또는 Task 생성자가 받아들이는 Action과 Func의 차이 때문입니다. 즉, Action으로 받아들일 때는 해당 메서드가 async void 유형이기 때문에 Task 개체는 단순히 action을 수행만 할 뿐 내부의 "await Task.Delay" 호출에 대한 배려를 하지 않고 진행을 계속하게 됩니다. 반면, Func<Task> 유형으로 전달하면 내부의 "await Task.Delay"가 반환하는 Task를 다시 받아서 연계하므로 중첩된 await의 효과가 나타나는 것입니다.<br /> <br /> 정리하면, 아래의 2개 코드는 완전히 다른 결과를 낳게 되는 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // 1) C# 컴파일러는 async 내부의 람다 메서드를 Func<Task>로 추론해 처리 return <span style='color: blue; font-weight: bold'>Task.Run</span>(<span style='color: blue; font-weight: bold'>async</span> () => { Console.WriteLine("Going to sleep ..."); await Task.Delay(1000); Console.WriteLine("... Woke up"); }); </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // 2) 사용자가 Action으로 명시를 했으므로 Task.Run은 내부의 Task 개체와 연동할 수 없음 <span style='color: blue; font-weight: bold'>Action action</span> = async () => { Console.WriteLine("Going to sleep ..."); await Task.Delay(5000); Console.WriteLine("... Woke up"); }; return <span style='color: blue; font-weight: bold'>Task.Run(action);</span> </pre> <br /> 즉, Task.Run에 Func<Task> 유형의 델리게이트를 전달했는지, Action 유형의 델리게이트를 전달했느냐에 따라 비동기 실행 결과가 달라진다는 것을 염두에 두어야 합니다.<br /> <br /> 참고로, Task 생성자는 오직 Action만 받기 때문에 "<a target='tab' href='https://dev.to/glsolaria/beware-of-how-you-create-a-task-23h9'>Beware of how you create a Task</a>" 글의 코드처럼 Task 생성과 실행 단계를 나누면 필연적으로 Action 내부의 Task 연동이 안 됩니다. (그나저나, Task.Run에는 Action과 Func를 모두 받게 하면서 왜 생성자에는 Action 하나만 받게 한 걸까요? ^^)<br /> <br /> <hr style='width: 50%' /><br /> <a name='run_diff'></a> <br /> 한 가지 더 알아볼까요? ^^ 보통 <a target='tab' href='https://devblogs.microsoft.com/pfxteam/task-run-vs-task-factory-startnew/'>Task.Run은 Task.Factory.StartNew로 다음의 옵션을 주었을 때 완전히 같다고 설명</a>합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Task.Factory.StartNew(func, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default) </pre> <br /> 그렇다면, 위의 코드를 StartNew에 대입해 보면 어떻게 될까요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>Func<Task></span> func = async () => { Console.WriteLine("Going to sleep ..."); await Task.Delay(5000); Console.WriteLine("... Woke up"); }; <span style='color: blue; font-weight: bold'>await Task.Factory.StartNew(func);</span> Console.WriteLine("Run complete"); /* 출력 결과 Going to sleep ... Run complete ... Woke up */ </pre> <br /> 실행해 보면, Task.Run과는 다르게 "Run complete" 메시지가 중간에 나옵니다. 그러니까, 완전히 동일하지는 않고 반환값 처리에서 Run은 Task를 반환하는 반면, StartNew는 Task<Task<T>>를 반환하는 차이가 있습니다. (자세한 내용은 "<a target='tab' href='https://devblogs.microsoft.com/pfxteam/task-run-vs-task-factory-startnew/'>Task.Run vs Task.Factory.StartNew</a>" 글에 예제 코드와 곁들인 설명을 참조하세요.)<br /> <br /> 따라서, StartNew의 경우 내부 Task를 연동하기 위해 Unwrap 메서드를 호출해주면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Func<Task> func = async () => { Console.WriteLine("Going to sleep ..."); await Task.Delay(5000); Console.WriteLine("... Woke up"); }; await Task.Factory.StartNew(func)<span style='color: blue; font-weight: bold'>.Unwrap()</span>; /* 출력 결과 Going to sleep ... ... Woke up Run complete */ </pre> <br /> 이제서야 의도했던 대로 Task.Run과 동일하게 동작합니다.<br /> <br /> 그러니까, 좀 더 엄밀히 말하면 Task.Run과 동일한 것은 "Task.Factory.StartNew(func, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default).Unwrap()"입니다.<br /> <br /> 혹은, 매우 희한한 구문이지만 await을 두 번 해서 StartNew가 반환한 Task<Task<TResult>>를 내부의 Task<TResult>를 활용하도록 바꾸는 것도 가능합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Func<Task> func = async () => { Console.WriteLine("Going to sleep ..."); await Task.Delay(5000); Console.WriteLine("... Woke up"); }; <span style='color: blue; font-weight: bold'>await await</span> Task.Factory.StartNew(func); </pre> <br /> 결과는 Unwrap과 동일하게 나옵니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 원래 글은 "<a target='tab' href='https://dev.to/glsolaria/beware-of-how-you-create-a-task-23h9'>Beware of how you create a Task</a>"에서 시작했지만, 어쩌다 보니 "<a target='tab' href='https://devblogs.microsoft.com/pfxteam/task-run-vs-task-factory-startnew/'>Task.Run vs Task.Factory.StartNew</a>" 글까지 자연스럽게 설명이 되었군요. ^^<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1865&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1934
(왼쪽의 숫자를 입력해야 합니다.)