성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
글쓰기
제목
이름
암호
전자우편
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# - async를 Task 타입이 아닌 사용자 정의 타입에 적용하는 방법</h1> <p> 이전 글에서, await의 대상이 되는 메서드의 반환값을 우리가 정의한 타입으로 바꾸는 방법을 설명했습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# - await을 Task 타입이 아닌 사용자 정의 타입에 적용하는 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11456'>http://www.sysnet.pe.kr/2/0/11456</a> </pre> <br /> 이번엔 저 글의 예제에 사용된 MyTask를 async 메서드의 반환 타입으로도 사용할 수 있도록 확장해 보겠습니다. <br /> <br /> <hr style='width: 50%' /><br /> <br /> 지난번 예제에서 Task와 MyTask를 반환하는 메서드를 각각 async 예약어를 붙여 코드를 수정하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > private static <span style='color: blue; font-weight: bold'>async Task</span> TaskAwait() { <span style='color: blue; font-weight: bold'>await</span> new TaskFactory().StartNew(() => { Thread.Sleep(5000); }); } // CS1983 컴파일 에러 발생 private static <span style='color: blue; font-weight: bold'>async MyTask</span> TestAwait() { MyTask task = new MyTask( () => { Thread.Sleep(5000); }); <span style='color: blue; font-weight: bold'>await</span> task; } </pre> <br /> 빌드 시 MyTask를 반환하는 코드에 대해 다음과 같은 컴파일 오류가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > CS1983 The return type of an async method must be void, Task or Task<T> </pre> <br /> 의미인즉, async 메서드는 반환 타입이 반드시 void, Task, Task<T> 중의 하나여야 한다는 것입니다. 하지만 <a target='tab' href='https://docs.microsoft.com/ko-kr/dotnet/csharp/whats-new/csharp-7'>C# 7.0부터 "일반화된 비동기 반환 형식"을 지원</a>하면서 그 외의 타입으로도 가능하게 바뀌었습니다. 일례로 <a target='tab' href='https://www.nuget.org/packages/System.Threading.Tasks.Extensions/'>ValueTask<T> 타입</a>이 그것입니다.<br /> <br /> 사용자 정의 타입을 async 메서드의 반환 타입으로 쓰려면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Return Any (Task-Like) Type From An Async Method ; <a target='tab' href='http://blog.i3arnon.com/2016/07/25/arbitrary-async-returns/'>http://blog.i3arnon.com/2016/07/25/arbitrary-async-returns/</a> </pre> <br /> 다음의 2가지 타입을 더 구현해야 합니다.<br /> <br /> <ul> <li>사용자 정의 Method Builder 타입 정의</li> <li>사용자 정의 Awaiter 타입 정의</li> </ul> <br /> 따라서 우리가 구현한 MyTask에 대한 사용자 정의 Method Builder를 다음과 같이 구현할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public struct AsyncMyTaskMethodBuilder { AsyncTaskMethodBuilder _methodBuilder; <span style='color: blue; font-weight: bold'>public MyTask Task { get { return new MyTask(_methodBuilder.Task); } }</span> public static AsyncMyTaskMethodBuilder Create() { return new AsyncMyTaskMethodBuilder { _methodBuilder = AsyncTaskMethodBuilder.Create() }; } public void SetException(Exception exception) { _methodBuilder.SetException(exception); } public void SetResult() { _methodBuilder.SetResult(); } public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine { _methodBuilder.AwaitOnCompleted(ref awaiter, ref stateMachine); } public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine { _methodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); } public void SetStateMachine(IAsyncStateMachine stateMachine) { _methodBuilder.SetStateMachine(stateMachine); } public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine { _methodBuilder.Start(ref stateMachine); } } </pre> <br /> 그다음 Awaiter를 이런 식으로 정의해 주면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public struct MyTaskAwaiter : ICriticalNotifyCompletion, INotifyCompletion { MyTask _task; public MyTaskAwaiter(MyTask t) { _task = t; } public bool IsCompleted { get { return _task.IsCompleted; } } public void GetResult() { } public void OnCompleted(Action continuation) { if (_task.Task == null) { _task.AddContinuation(continuation); } else { _task.Task.GetAwaiter().OnCompleted(continuation); } } public void UnsafeOnCompleted(Action continuation) { if (_task.Task == null) { _task.AddContinuation(continuation); } else { _task.Task.GetAwaiter().UnsafeOnCompleted(continuation); } } } </pre> <br /> 마지막으로 우리가 정의한 MyTask 타입에 새롭게 정의한 Method Builder 타입을 연결하고 기타 작업을 추가해야 합니다.<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'>[AsyncMethodBuilder(typeof(AsyncMyTaskMethodBuilder))]</span> public class MyTask { Thread _t; Task _task; internal Task Task { get { return _task; } } bool _isCompleted; List<Action> _continuation = new List<Action>(); public void AddContinuation(Action action) { if (_t == null) { _task.ContinueWith((t) => { action(); }); } else { _continuation.Add(action); } } public bool IsCompleted { get { if (_t == null) { return _task.IsCompleted; } return _isCompleted; } } public MyTask(Task task) { _task = task; } public MyTask(Action action) { _t = new Thread( (ThreadStart)( () => { action(); _isCompleted = true; foreach (var item in _continuation) { item(); } } ) ); _t.Start(); } public MyTaskAwaiter GetAwaiter() { MyTaskAwaiter ta = new MyTaskAwaiter(this); return ta; } } </pre> <br /> 이렇게 변경한 후, 다시 빌드하면 컴파일 오류가 발생했던 TestAwait 메서드가 정상적으로 빌드되는 것을 확인할 수 있습니다.<br /> <br /> (<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1242&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 구현을 보면 알겠지만, 사실 async/await 관련 구현은 Task 객체를 기반으로 사용자 정의 타입을 정의했을 때 부드럽게 연결이 됩니다. 즉, async 메서드에 사용자 정의 타입을 반환할 수 있다고는 하지만 결국 Task 타입을 기반으로 한 사용자 정의 타입인 경우에만 구현이 용이하게 되어 있습니다.<br /> <br /> 그나저나, "<a target='tab' href='http://www.sysnet.pe.kr/2/0/11456'>C# - await을 Task 타입이 아닌 사용자 정의 타입에 적용하는 방법</a>" 글을 쓸 때에도 마지막에 C# 5.0부터 가능했었다는 말을 했었는데요. 하지만, C# 7.0 문서에서는 await이 아닌 async 메서드를 대상으로 "일반화된 비동기 반환 형식"이 가능하다고 했기 때문에 일단 저 글에서는 마지막 내용을 취소선으로 표시했습니다.<br /> <br /> 그런데, 이번 글에 쓴 코드 역시 C# 5.0 컴파일러에서도 잘 빌드가 됩니다. 즉, "일반화된 비동기 반환 형식"은 C# 7.0부터 제공한 기능이라기보다는 그냥 7.0부터 본격적으로 소개를 한 것입니다. 사실 이에 대해서는 ValueTask의 소개 시점을 유추해 보면 좀 이상하긴 했습니다. 왜냐하면 최초 ValueTask가,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > System.Threading.Tasks.Extensions ; <a target='tab' href='https://www.nuget.org/packages/System.Threading.Tasks.Extensions/'>https://www.nuget.org/packages/System.Threading.Tasks.Extensions/</a> </pre> <br /> 릴리스된 날짜가 2016년 5월인 반면 C# 7.0은 2017년 3월이었기 때문에 C# 7.0의 "<a target='tab' href='https://blogs.msdn.microsoft.com/dotnet/2016/08/24/whats-new-in-csharp-7-0/'>Generalized async return types</a>" 덕분에 구현이 가능했던 것은 아니기 때문입니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
4600
(왼쪽의 숫자를 입력해야 합니다.)