다음은 비동기 해결을 사용하여 차이점과 다양한 문제를 설명하기 위해 최근에 사용한 코드 조각의 시퀀스입니다.
GUI 기반 애플리케이션에 많은 시간이 걸리는 이벤트 핸들러가있어서이를 비동기로 만들고 싶다고 가정 해 보겠습니다. 시작하는 동기 논리는 다음과 같습니다.
while (true) {
string result = LoadNextItem().Result;
if (result.Contains("target")) {
Counter.Value = result.Length;
break;
}
}
LoadNextItem은 작업을 반환하며 결국 검사하려는 결과를 생성합니다. 현재 결과가 찾고있는 결과이면 UI에서 일부 카운터 값을 업데이트하고 메서드에서 반환합니다. 그렇지 않으면 LoadNextItem에서 더 많은 항목을 계속 처리합니다.
비동기 버전에 대한 첫 번째 아이디어 : 계속 사용하십시오! 그리고 당분간 루핑 부분을 무시합시다. 내 말은, 무엇이 잘못 될 수 있습니까?
return LoadNextItem().ContinueWith(t => {
string result = t.Result;
if (result.Contains("target")) {
Counter.Value = result.Length;
}
});
좋습니다. 이제 차단하지 않는 메서드가 있습니다! 대신 충돌합니다. UI 컨트롤에 대한 모든 업데이트는 UI 스레드에서 발생해야하므로이를 고려해야합니다. 고맙게도 연속 작업을 예약하는 방법을 지정하는 옵션이 있으며 이에 대한 기본 옵션이 있습니다.
return LoadNextItem().ContinueWith(t => {
string result = t.Result;
if (result.Contains("target")) {
Counter.Value = result.Length;
}
},
TaskScheduler.FromCurrentSynchronizationContext());
좋습니다. 이제 충돌하지 않는 방법이 있습니다! 대신 조용히 실패합니다. 연속 작업은 이전 작업의 상태와 관련이없는 별도의 작업 자체입니다. 따라서 LoadNextItem에 오류가 있더라도 호출자는 성공적으로 완료된 작업 만 볼 수 있습니다. 좋아요, 예외가 있다면 그냥 넘겨주세요 :
return LoadNextItem().ContinueWith(t => {
if (t.Exception != null) {
throw t.Exception.InnerException;
}
string result = t.Result;
if (result.Contains("target")) {
Counter.Value = result.Length;
}
},
TaskScheduler.FromCurrentSynchronizationContext());
이제 실제로 작동합니다. 단일 항목의 경우. 이제 루핑은 어떻습니까? 결과적으로 원래 동기 버전의 논리와 동일한 솔루션은 다음과 같습니다.
Task AsyncLoop() {
return AsyncLoopTask().ContinueWith(t =>
Counter.Value = t.Result,
TaskScheduler.FromCurrentSynchronizationContext());
}
Task<int> AsyncLoopTask() {
var tcs = new TaskCompletionSource<int>();
DoIteration(tcs);
return tcs.Task;
}
void DoIteration(TaskCompletionSource<int> tcs) {
LoadNextItem().ContinueWith(t => {
if (t.Exception != null) {
tcs.TrySetException(t.Exception.InnerException);
} else if (t.Result.Contains("target")) {
tcs.TrySetResult(t.Result.Length);
} else {
DoIteration(tcs);
}});
}
또는 위의 모든 것 대신 async를 사용하여 동일한 작업을 수행 할 수 있습니다.
async Task AsyncLoop() {
while (true) {
string result = await LoadNextItem();
if (result.Contains("target")) {
Counter.Value = result.Length;
break;
}
}
}
이제 훨씬 더 멋지지 않습니까?
Wait
두 번째 예제에서 전화를 한 후 두 조각은 (대부분) 해당 될 것이다.