.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에 따라 라이센스가 부여됩니다.