C#의 이벤트 기반 비동기; 일반적인 리팩토링이 가능합니까?
-
05-07-2019 - |
문제
WebClient와 같은 일부 API는 사용합니다 이벤트 기반 비동기 패턴. 이것은 단순 해 보이고 느슨하게 결합 된 앱 (예 : UI의 배경 작업자)에서 잘 작동하지만 잘 어울리지는 않습니다.
예를 들어, 다중 스레드가있는 프로그램이 있으므로 비동기 작업이 차단되지 않습니다. (이것이 서버 앱에 들어가서 수백 번 전화를한다고 상상해보십시오. 스레드 풀 스레드를 차단하고 싶지 않습니다.) 우리는 3 개의 로컬 변수 ( "State")를 얻은 다음 2 개의 비동기 호출을 만듭니다. 두 번째 요청에 먼저 공급됩니다 (따라서 평행하게 갈 수 없습니다). 상태도 돌연변이 할 수 있습니다 (추가하기 쉽음).
WebClient를 사용하면 상황이 다음과 같이 끝납니다 (또는 폐쇄처럼 행동하기 위해 많은 객체를 만들게됩니다) :
using System;
using System.Net;
class Program
{
static void onEx(Exception ex) {
Console.WriteLine(ex.ToString());
}
static void Main() {
var url1 = new Uri(Console.ReadLine());
var url2 = new Uri(Console.ReadLine());
var someData = Console.ReadLine();
var webThingy = new WebClient();
DownloadDataCompletedEventHandler first = null;
webThingy.DownloadDataCompleted += first = (o, res1) => {
if (res1.Error != null) {
onEx(res1.Error);
return;
}
webThingy.DownloadDataCompleted -= first;
webThingy.DownloadDataCompleted += (o2, res2) => {
if (res2.Error != null) {
onEx(res2.Error);
return;
}
try {
Console.WriteLine(someData + res2.Result);
} catch (Exception ex) { onEx(ex); }
};
try {
webThingy.DownloadDataAsync(new Uri(url2.ToString() + "?data=" + res1.Result));
} catch (Exception ex) { onEx(ex); }
};
try {
webThingy.DownloadDataAsync(url1);
} catch (Exception ex) { onEx(ex); }
Console.WriteLine("Keeping process alive");
Console.ReadLine();
}
}
이 이벤트 기반 비동기 패턴을 리팩터링하는 일반적인 방법이 있습니까? (즉, 다음과 같은 각 API에 대해 자세한 확장 방법을 작성할 필요는 없습니까?) 시작 XXX와 ENDXXX는 쉽게 만들 수 있지만이 이벤트 방식은 어떤 식 으로든 제공하지 않는 것 같습니다.
해결책
당신은 조사하고 싶을 수도 있습니다 F#
. F#
«Workflow»기능 으로이 코딩을 자동화 할 수 있습니다. '08 PDC 프레젠테이션 F#
불리는 표준 라이브러리 워크 플로우를 사용하여 비동기 웹 요청을 처리합니다. async
,이를 처리합니다 BeginXXX
/EndXXX
패턴이지만 많은 어려움없이 이벤트 패턴에 대한 워크 플로우를 작성하거나 통조림을 찾을 수 있습니다. 그리고 f#은 C#과 잘 작동합니다.
다른 팁
과거에는 반복자 메소드를 사용 하여이 구현을 구현했습니다. 다른 URL을 요청할 때마다 "수율 리턴"을 사용하여 제어를 기본 프로그램으로 다시 전달합니다. 요청이 완료되면 기본 프로그램은 반복자로 다시 호출하여 다음 작업을 실행합니다.
C# 컴파일러를 효과적으로 사용하여 상태 머신을 작성합니다. 장점은 반복자 방법에 정상적인 C# 코드를 작성하여 모든 것을 추진할 수 있다는 것입니다.
using System;
using System.Collections.Generic;
using System.Net;
class Program
{
static void onEx(Exception ex) {
Console.WriteLine(ex.ToString());
}
static IEnumerable<Uri> Downloader(Func<DownloadDataCompletedEventArgs> getLastResult) {
Uri url1 = new Uri(Console.ReadLine());
Uri url2 = new Uri(Console.ReadLine());
string someData = Console.ReadLine();
yield return url1;
DownloadDataCompletedEventArgs res1 = getLastResult();
yield return new Uri(url2.ToString() + "?data=" + res1.Result);
DownloadDataCompletedEventArgs res2 = getLastResult();
Console.WriteLine(someData + res2.Result);
}
static void StartNextRequest(WebClient webThingy, IEnumerator<Uri> enumerator) {
if (enumerator.MoveNext()) {
Uri uri = enumerator.Current;
try {
Console.WriteLine("Requesting {0}", uri);
webThingy.DownloadDataAsync(uri);
} catch (Exception ex) { onEx(ex); }
}
else
Console.WriteLine("Finished");
}
static void Main() {
DownloadDataCompletedEventArgs lastResult = null;
Func<DownloadDataCompletedEventArgs> getLastResult = delegate { return lastResult; };
IEnumerable<Uri> enumerable = Downloader(getLastResult);
using (IEnumerator<Uri> enumerator = enumerable.GetEnumerator())
{
WebClient webThingy = new WebClient();
webThingy.DownloadDataCompleted += delegate(object sender, DownloadDataCompletedEventArgs e) {
if (e.Error == null) {
lastResult = e;
StartNextRequest(webThingy, enumerator);
}
else
onEx(e.Error);
};
StartNextRequest(webThingy, enumerator);
}
Console.WriteLine("Keeping process alive");
Console.ReadLine();
}
}