다음 예에서 누군가 await
와 ContinueWith
동의어인지 아닌지 설명 할 수 있습니까 ? 나는 처음으로 TPL을 사용하려고 노력하고 있으며 모든 문서를 읽었지만 차이점을 이해하지 못합니다.
기다립니다 :
String webText = await getWebPage(uri);
await parseData(webText);
ContinueWith :
Task<String> webText = new Task<String>(() => getWebPage(uri));
Task continue = webText.ContinueWith((task) => parseData(task.Result));
webText.Start();
continue.Wait();
특정 상황에서 하나가 다른 것보다 선호됩니까?
답변
두 번째 코드에서는 연속 작업이 완료 될 때까지 동 기적으로 대기합니다. 첫 번째 버전에서 메서드는 await
아직 완료되지 않은 첫 번째 표현식에 도달 하자마자 호출자에게 반환됩니다 .
둘 다 연속을 예약한다는 점에서 매우 유사하지만 제어 흐름이 약간 복잡해지면 코드 await
가 훨씬 더 간단 해 집니다 . 또한 Servy가 주석에서 언급했듯이 작업을 기다리는 것은 일반적으로 더 간단한 오류 처리로 이어지는 집계 예외를 “언 래핑”합니다. 또한를 사용 await
하면 호출 컨텍스트에서 연속을 암시 적으로 예약합니다 (를 사용하지 않는 경우 ConfigureAwait
). “수동으로”수행 할 수없는 것은 아니지만 .NET으로 수행하는 것이 훨씬 쉽습니다 await
.
난 당신이 모두 작업의 약간 큰 순서를 구현하려고 제안 await
하고 Task.ContinueWith
– 그것은 진짜 눈을 뜨게 할 수있다.
답변
다음은 비동기 해결을 사용하여 차이점과 다양한 문제를 설명하기 위해 최근에 사용한 코드 조각의 시퀀스입니다.
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;
}
}
}
이제 훨씬 더 멋지지 않습니까?