.NET 의 비동기 프로 그래 밍-continuation passing style 및 yield 를 사용 하여 비동기 화
                                            
 26413 단어  .NET비동기 프로 그래 밍yield비동기
                    
CPS
우선,다음 방법 을 살 펴 보 자.
   1: public int Add(int a, int b)     2: {     3:     return a + b;     4: }  우 리 는 일반적으로 이렇게 그것 을 호출한다.
   1: Print(Add(5, 6))     2:       3: public void Print(int result)     4: {     5:     Console.WriteLine(result);     6: }  만약 우리 가 CPS 방식 으로 위의 코드 를 작성 한다 면:
   1: public void Add(int a, int b, Action<int> continueWith)     2: {     3:     continueWith(a+b);     4: }     5:       6: Add(5, 6, (ret) => Print(ret));  마치 우리 가 방법 을 거꾸로 하 는 것 처럼 우 리 는 더 이상 직접 방법의 결과 로 돌아 가 는 것 이 아니다.우리 가 지금 하고 있 는 것 은 하나의 의뢰 를 받 는 것 이다.이 의뢰 는 내 가 이 방법 을 연산 한 후에 무엇 을 해 야 하 는 지 를 나타 내 는 것 이다.바로 전설의 contine 이다.여기 서 Add 의 continue 는 Print 입 니 다.
위 와 같은 코드 예시 뿐만 아니 라한 방법 에서 이 문장 뒤에 실 행 된 문장 은 모두 이 문장의 contine 이 라 고 할 수 있다.
CPS 와 Async
그럼 누가 물 어 볼 수도 있 겠 네요.이렇게 많은 말 을 하 는 것 이 비동기 와 무슨 관계 가 있 습 니까?네,비동기 와 큰 관계 가 있 습 니 다.이전 글 을 돌 이 켜 보면 전형 적 인 비동기 모드 는 모두 Begin 으로 시작 하 는 방법 으로 비동기 요청 을 하고 이 방법 에 반전(callback)을 보 냅 니 다.비동기 실행 이 끝 난 후에 이 반전 이 실 행 됩 니 다.그러면 우 리 는 이 비동기 가 요구 하 는 contine 이 라 고 할 수 있 습 니 다.
   1: stream.BeginRead(buffer, 0, 1024, continueWith, null)  이게 무슨 소 용이 야?그럼 우리 가 원 하 는 비동기 코드 부터 살 펴 보 자.
   1: var request = HttpWebRequest.Create("http://www.google.com");     2: var asyncResult1 = request.BeginGetResponse(...);     3: var response = request.EndGetResponse(asyncResult1);     4: using(stream = response.GetResponseStream())     5: {     6:     var asyncResult2 = stream.BeginRead(buffer, 0, 1024, ...);     7:     var actualRead = stream.EndRead(asyncResult2);     8: }  맞 아,우 리 는 동기 화 방식 처럼 비동기 코드 를 만 들 고 싶 어.나 는 그렇게 많은 반전 을 싫어한다.특히 한 링 에 한 링 을 끼 워 넣 은 반전 을 싫어한다.
앞에서 CPS 에 대한 토론 을 참조 하 십시오.request.Bigin GetResponse 이후 의 코드 는 모두 contine 입 니 다.만약 에 제 가 contine 을 얻 을 수 있 는 메커니즘 이 있다 면 제 가 실 행 된 후에 contine 을 호출 하 는 것 이 좋 겠 습 니 다.안 타 깝 게 도 C\#Scheme 과 같은 제어 연산 자 call/cc 가 contine 을 가 져 오지 않 았 습 니 다.
생각 이 여기까지 끊 긴 것 같다.그러나 우 리 는 각 도 를 바 꾸 어 생각해 볼 수 있 습 니까?만약 에 우리 가 위의 코드 에 표 지 를 추가 할 수 있다 면 모든 비동기 요청 이 시 작 된 곳 에 표 지 를 추가 하고 표지 의 다음 부분 은 contine 입 니 다.
var request = HttpWebRequest.Create("http://www.google.com");    1 var asyncResult1 = request.BeginGetResponse(...);  var response = request.EndGetResponse(asyncResult1);  using(stream = response.GetResponseStream())  {        2 var asyncResult2 = stream.BeginRead(buffer, 0, 1024, ...);      var actualRead = stream.EndRead(asyncResult2);  }  표지 1 에 실 행 했 을 때 바로 돌아 가 고 이번 집행 은 표지 1 에 만 실 행 된 것 을 기억 합 니 다.비동기 요청 이 끝 난 후에 마지막 에 표지 1 에 실 행 된 것 을 알 게 되 었 습 니 다.그러면 이때 표지 1 의 다음 줄 에서 실 행 됩 니 다.표지 2 에 실 행 했 을 때 또 하나의 비동기 요청 을 만 났 습 니 다.바로 표지 2 에 실 행 된 것 을 기억 합 니 다.그리고 요청 이 완료 되면 표지 2 의 다음 줄 에서 실행 을 재 개 합 니 다.그러면 현재 의 임 무 는 표 지 를 하고 비동기 요청 이 끝 난 후에 표지 위치 에서 어떻게 다시 실행 하 는 지 하 는 것 이다.
yield 와 비동기
C\#2.0 에 추 가 된 교체 기 특성 을 잘 알 고 있다 면,yield 는 우리 가 표 지 를 할 수 있 는 물건 이라는 것 을 알 게 될 것 이다.다음 코드 보기:
   1: public IEnumerator<int> Demo()     2: {     3:     //code 1     4:     yield return 1;     5:     //code 2     6:     yield return 2;     7:     //code 3     8:     yield return 3;     9: }  컴 파일 을 통 해 다음 과 같은 코드 를 생 성 합 니 다.
   1: public IEnumerator<int> Demo()     2: {     3:    return new GeneratedEnumerator();     4: }     5:       6: public class GeneratedEnumerator     7: {     8:     private int state = 0;     9:      10:     private int currentValue = 0;    11:         12:     public bool MoveNext()    13:     {    14:         switch(state)    15:         {    16:             case 0:    17:                 //code 1    18:                 currentValue = 1;    19:                 state = 1;    20:                 return true;    21:             case 1:    22:                 //code 2    23:                 currentValue = 2;    24:                 state = 2;    25:                 return true;    26:             case 2:    27:                 //code 3    28:                 currentValue = 3;    29:                 state = 3;    30:                 return true;    31:             default:return false;    32:         }    33:     }    34:         35:     public int Current{get{return currentValue;}}    36: }  네,C\#컴 파일 러 가 상태 기 로 번역 되 었 습 니 다.yield return 은 마치 많은 표 시 를 한 것 같 습 니 다.MoveNext 는 호출 할 때마다 다음 yield return 이전 코드 를 실행 하고 바로 돌아 갑 니 다.
자,이제 표 시 를 하 는 기능 이 생 겼 습 니 다.우 리 는 어떻게 비동기 요청 이 끝 난 후에 호출 을 재 개 합 니까?위의 코드 를 통 해 당신 은 이미 생각 했 을 것 입 니 다.우 리 는 여기에서 호출 을 재 개 하려 면 MoveNext 를 다시 호출 하면 됩 니 다.그 상태 기 회 는 우 리 를 도와 모든 것 을 처리 할 수 있 습 니 다.
그러면 우 리 는 우리 의 비동기 코드 를 개조 합 니 다.
   1: public IEnumerator<int> Download()     2: {     3:     var request = HttpWebRequest.Create("http://www.google.com");     4:     var asyncResult1 = request.BeginGetResponse(...);     5:     yield return 1;     6:     var response = request.EndGetResponse(asyncResult1);     7:     using(stream = response.GetResponseStream())     8:     {     9:         var asyncResult2 = stream.BeginRead(buffer, 0, 1024, ...);    10:         yield return 1;    11:         var actualRead = stream.EndRead(asyncResult2);    12:     }    13: }  표 시 를 다 했 습 니 다.비동기 호출 이 끝나 면 MoveNext 를 실행 하 는 방법 을 고려 해 보 세 요.
하하,비동기 호출 된 AsyncCallback 리 셋 기억 나 세 요?비동기 요청 이 끝나 면 호출 될 겁 니 다.만약 우리 가 비동기 요청 을 한 Begin XXX 방법 에 AsyncCallback 을 전달한다 면,이 리 셋 에서 MoveNext 를 호출 하 는 것 은 어 떻 습 니까?
   1: public IEnumerator<int> Download(Context context)     2: {     3:     var request = HttpWebRequest.Create("http://www.google.com");     4:     var asyncResult1 = request.BeginGetResponse(context.Continue(),null);     5:     yield return 1;     6:     var response = request.EndGetResponse(asyncResult1);     7:     using(stream = response.GetResponseStream())     8:     {     9:         var asyncResult2 = stream.BeginRead(buffer, 0, 1024, context.Continue(),null);    10:         yield return 1;    11:         var actualRead = stream.EndRead(asyncResult2);    12:     }    13: }  Continue 방법의 정 의 는:
   1: public class Context     2: {     3:     //...     4:     private IEnumerator enumerator;     5:          6:     public AsyncCallback Continue()     7:     {     8:         return (ar) => enumerator.MoveNext();     9:     }    10: }  Continue 방법 을 호출 하기 전에 Context 클래스 는 다운로드 방법 이 있 는 IEnumerator 를 저장 해 야 합 니 다.따라서:
   1: public class Context     2: {     3:     //...     4:     private IEnumerator enumerator;     5:          6:     public AsyncCallback Continue()     7:     {     8:         return (ar) => enumerator.MoveNext();     9:     }    10:      11:     public void Run(IEnumerator enumerator)    12:     {    13:         this.enumerator = enumerator;    14:         enumerator.MoveNext();    15:     }    16: }  다운로드 방법 을 호출 하면 다음 과 같이 쓸 수 있 습 니 다.
   1: public void Main()     2: {     3:     Program p = new Program();     4:          5:     Context context = new Context();     6:     context.Run(p.Download(context));     7: }  실행 방식 이 다른 것 을 제외 하고 우 리 는 거의 동기 화 방식 처럼 비동기 코드 를 작성 할 수 있다.
전체 코드 는 다음 과 같 습 니 다.
   1: public class Context     2: {     3:     private IEnumerator enumerator;     4:          5:     public AsyncCallback Continue()     6:     {     7:         return (ar) => enumerator.MoveNext();     8:     }     9:      10:     public void Run(IEnumerator enumerator)    11:     {    12:         this.enumerator = enumerator;    13:         enumerator.MoveNext();    14:     }    15: }    16:      17: private void btnDownload_click(object sender,EventArgs e)    18: {    19:     Context context = new Context();    20:     context.Run(Download(context));    21: }    22:      23: private IEnumerator<int> Download(Context context)    24: {    25:     var request = HttpWebRequest.Create("http://www.google.com");    26:     var asyncResult1 = request.BeginGetResponse(context.Continue(),null);    27:     yield return 1;    28:     var response = request.EndGetResponse(asyncResult1);    29:     using(stream = response.GetResponseStream())    30:     {    31:         var asyncResult2 = stream.BeginRead(buffer, 0, 1024, context.Continue(),null);    32:         yield return 1;    33:         var actualRead = stream.EndRead(asyncResult2);    34:     }    35: }  눈치 채 셨 는 지 모 르 겠 습 니 다.우 리 는 비동기 코드 를 순서대로 작성 할 수 있 을 뿐만 아니 라 using 과 같은 구조 도 사용 할 수 있 습 니 다.이 코드 를 더 깊이 이해 하고 싶다 면 Reflector 를 사용 하여 교체 기 가 마지막 으로 생 성 한 코드 를 보 는 것 을 추천 합 니 다.나 는 여기 서 간단 한 설명 을 한다.
1.Context 의 Run 호출 시 Dowload 방법 을 호출 하여 IEnumerator 대상 을 얻 습 니 다.저 희 는 이 대상 을 Context 의 인 스 턴 스 필드 에 저장 하여 나중에 사용 할 수 있 도록 합 니 다.
2.이 IEnumerator 대상 의 MoveNext 방법 을 호출 합 니 다.이 방법 은 첫 번 째 yield return 위치 로 실 행 된 다음 에 되 돌아 갑 니 다.이때 request.Begin GetResponse 가 호출 되 었 습 니 다.이 때 스 레 드 는 다른 일 을 할 수 있 습 니 다.
3.BeginGetResponse 호출 시 Context 의 Continue 방법 을 통 해 리 셋 을 전 달 했 습 니 다.이 리 셋 에 서 는 방금 저 장 된 IEnumerator 대상 의 MoveNext 방법 을 실행 합 니 다.즉,BeginGetResponse 라 는 비동기 요청 이 실 행 된 후에 MoveNext 방법 을 호출 하여 흐름 을 제어 하고 다시 다운로드 방법 으로 돌아 가 다음 yield return 으로 실 행 됩 니 다.
총결산
본 고 를 정리 하면 우 리 는 우리 가 원 하 는 것 이 순서 스타일 의 코드 를 어떻게 CPS 방식 으로 바 꾸 는 지,어떻게 비동기 적 으로 이 코드 를 요청 하 는 contine 을 찾 는 지 알 게 되 었 다.C\#는 yield 라 는 메커니즘 을 제 공 했 기 때문에 C\#컴 파 일 러 는 상태 기 를 생산 하여 제어 권 을 호출 코드 와 호출 된 코드 사이 에서 교환 할 수 있 습 니 다.
주의해 야 할 것 은 본 논문 이 마지막 으로 실현 한 비동기 집행 방식 이 매우 초라 하기 때문에 제품 코드 에 절대 응용 할 수 없다 는 것 이다.이곳 은 단지 목적 을 시범 하기 위 한 것 이다.이 방면 에서 마이크로소프트 커 뮤 니 티 의 큰 소 Jeffrey Ritcher 는 우리 가 Power Threading 이라는 라 이브 러 리 를 개발 했다 고 생각 했 는데 그 안에 AsyncEnumerator 류 를 제공 하여 더욱 믿 을 만 한 실현 이 었 다.
마이크로소프트 가 자체 적 으로 로봇 개발 에 제공 한 CCR 도 비슷 한 실현 을 제공 했다.우 리 는 다음 문장 에서 이 두 종류의 라 이브 러 리 를 배 울 것 이다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Visual Studio 2017에서 SQLite를 사용한 Windows Forms 앱 개발Visual Studio 2017에서 SQLite를 사용하여 Windows Forms 앱을 개발해 보았습니다. 아직 서버 탐색기나 TableAdaptor를 사용한 GUI에서의 개발에는 대응하지 않는 것 같습니다. 이...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.