C # 5.0의 async-await 기능은 TPL과 어떻게 다릅니 까?
C # (및 VB)의 새로운 비동기 기능과 .NET 4.0의 작업 병렬 라이브러리 간에 차이점이 보이지 않습니다 . 예를 들어 여기에서 Eric Lippert의 코드 를 가져옵니다 .
async void ArchiveDocuments(List<Url> urls) {
Task archive = null;
for(int i = 0; i < urls.Count; ++i) {
var document = await FetchAsync(urls[i]);
if (archive != null)
await archive;
archive = ArchiveAsync(document);
}
}
await
키워드가 두 가지 용도로 사용 되는 것 같습니다 . 첫 번째 발생 ( FetchAsync
)은 "이 값이 메서드에서 나중에 사용되고 작업이 완료되지 않은 경우 계속하기 전에 완료 될 때까지 기다리십시오." 를 의미하는 것 같습니다 . 두 번째 인스턴스 ( archive
)는 "이 작업이 아직 완료되지 않은 경우 완료 될 때까지 지금 기다리 십시오." 라는 의미 인 것 같습니다 . 내가 틀렸다면 정정하십시오.
이렇게 쉽게 쓸 수 없을까요?
void ArchiveDocuments(List<Url> urls) {
for(int i = 0; i < urls.Count; ++i) {
var document = FetchAsync(urls[i]); // removed await
if (archive != null)
archive.Wait(); // changed to .Wait()
archive = ArchiveAsync(document.Result); // added .Result
}
}
첫 번째 는 값이 실제로 필요한 위치 로 , 두 번째 는 실제로 대기가 발생 await
하는 Task.Result
위치 로 대체되었습니다 . 기능은 이미 구현 되어 있으며 코드에서 실제로 일어나는 일에 훨씬 더 가깝습니다.await
Task.Wait()
(1)
(2)
async
메서드가 반복기와 유사한 상태 머신으로 다시 작성 된다는 것을 알고 있지만 어떤 이점이 있는지도 알 수 없습니다. 다른 스레드가 작동해야하는 코드 (예 : 다운로드)는 여전히 다른 스레드를 필요로하며, 그렇지 않은 코드 (예 : 파일 읽기)는 TPL을 사용하여 단일 스레드 만 사용할 수 있습니다.
나는 분명히 여기에 거대한 것을 놓치고있다. 아무도 이것을 조금 더 잘 이해하도록 도울 수 있습니까?
여기에서 오해가 발생한다고 생각합니다.
await 키워드가 두 가지 다른 용도로 사용되는 것 같습니다. 첫 번째 발생 (FetchAsync)은 "이 값이 메서드에서 나중에 사용되고 작업이 완료되지 않은 경우 계속하기 전에 완료 될 때까지 기다립니다"를 의미하는 것 같습니다. 두 번째 인스턴스 (아카이브)는 "이 작업이 아직 완료되지 않은 경우 완료 될 때까지 지금 기다리십시오."라는 의미 인 것 같습니다. 내가 틀렸다면 정정하십시오.
이것은 실제로 완전히 잘못된 것입니다. 둘 다 같은 의미를 가지고 있습니다.
첫 번째 경우 :
var document = await FetchAsync(urls[i]);
여기서 일어나는 일은 런타임이 "FetchAsync 호출을 시작한 다음이 메서드를 호출하는 스레드에 현재 실행 지점을 반환"이라고 말합니다. 여기에는 "대기"가 없습니다. 대신 실행이 호출 동기화 컨텍스트로 돌아가고 상황이 계속 변동합니다. 미래의 어느 시점에서 FetchAsync의 작업이 완료되고 그 시점에서이 코드는 호출 스레드의 동기화 컨텍스트에서 다시 시작되고 다음 문 (문서 변수 할당)이 발생합니다.
그런 다음 두 번째 await 호출이 발생할 때까지 실행이 계속됩니다. 이때 동일한 일이 발생합니다. Task<T>
(아카이브)가 완료되지 않으면 실행이 호출 컨텍스트로 해제됩니다. 그렇지 않으면 아카이브가 설정됩니다.
두 번째 경우에는 상황이 매우 다릅니다. 여기서는 명시 적으로 차단하고 있습니다. 즉, 전체 메서드가 완료 될 때까지 호출 동기화 컨텍스트가 코드를 실행할 기회를 얻지 못합니다. 물론, 여전히 비 동시성이 존재하지만 비 동시성은이 코드 블록 내에 완전히 포함되어 있습니다. 모든 코드가 완료 될 때까지이 붙여 넣은 코드 외부의 코드는이 스레드에서 발생하지 않습니다.
큰 차이가 있습니다.
Wait()
블록, 차단 await
하지 않습니다. ArchiveDocuments()
GUI 스레드에서 의 비동기 버전을 실행하면 가져 오기 및 보관 작업이 실행되는 동안 GUI가 계속 응답합니다. 에서 TPL 버전을 사용하면 Wait()
GUI가 차단됩니다.
async
스레드를 도입하지 않고이 작업을 수행 할 수 있다는 점에 유의 하십시오 await
. 제어는 단순히 메시지 루프로 리턴됩니다. 대기중인 작업이 완료되면 나머지 메서드 (계속)가 메시지 루프의 대기열에 추가되고 GUI 스레드가 중단 된 ArchiveDocuments
지점에서 계속 실행 됩니다.
Anders는 그가 한 Channel 9 Live 인터뷰에서 매우 간결한 답변으로 요약했습니다. 나는 그것을 강력히 추천한다
새로운 Async 및 await 키워드를 사용하면 애플리케이션에서 동시성 을 오케스트레이션 할 수 있습니다 . 실제로 애플리케이션에 동시성을 도입하지는 않습니다.
TPL, 특히 작업은 실제로 동시에 작업을 수행하는 데 사용할 수있는 한 가지 방법 입니다. 새로운 async 및 await 키워드를 사용하면 이러한 동시 작업을 "동기"또는 "선형"방식 으로 구성 할 수 있습니다 .
따라서 실제 컴퓨팅이 동시에 발생하거나 발생하지 않는 동안에도 프로그램에서 선형 제어 흐름을 작성할 수 있습니다. 계산이 동시에 발생하면 await 및 async를 사용하여 이러한 작업 을 구성 할 수 있습니다 .
제어의 프로그램 흐름을 상태 머신으로 전환하는 능력은 이러한 새로운 키워드를 흥미롭게 만듭니다. 값이 아니라 제어 를 산출 하는 것으로 생각하십시오 .
새로운 기능에 대해 이야기하는 Anders 의이 채널 9 비디오 를 확인하십시오 .
The problem here is that the signature of ArchiveDocuments
is misleading. It has an explicit return of void
but really the return is Task
. To me void implies synchronous as there is no way to "wait" for it to finish. Consider the alternate signature of the function.
async Task ArchiveDocuments(List<Url> urls) {
...
}
To me when it's written this way the difference is much more obvious. The ArchiveDocuments
function is not one that completes synchronously but will finish later.
The call to FetchAsync()
will still block until it completes (unless a statement within calls await
?) The key is that control is returned to the caller (because the ArchiveDocuments
method itself is declared as async
). So the caller can happily continue processing UI logic, respond to events, etc.
When FetchAsync()
completes, it interrupts the caller to finish the loop. It hits ArchiveAsync()
and blocks, but ArchiveAsync()
probably just creates a new task, starts it, and returns the task. This allows the second loop to begin, while the task is processing.
The second loop hits FetchAsync()
and blocks, returning control to the caller. When FetchAsync()
completes, it again interrupts the caller to continue processing. It then hits await archive
, which returns control to the caller until the Task
created in loop 1 completes. Once that task is complete, the caller is again interrupted, and the second loop calls ArchiveAsync()
, which gets a started task and begins loop 3, repeat ad nauseum.
The key is returning control to the caller while the heavy lifters are executing.
The await keyword does not introduce concurrency. It is like the yield keyword, it tells the compiler to restructure your code into lambda controlled by a state machine.
To see what await code would look like without 'await' see this excellent link: http://blogs.msdn.com/b/windowsappdev/archive/2012/04/24/diving-deep-with-winrt-and-await.aspx
'Program Tip' 카테고리의 다른 글
Spring-POST 후 리디렉션 (유효성 검사 오류 포함) (0) | 2020.11.30 |
---|---|
JavaScript는 버튼 클릭시 페이지를로드합니다. (0) | 2020.11.30 |
Mac에서 PATH 변수 편집 (0) | 2020.11.29 |
레일의 파일 다운로드 링크 (0) | 2020.11.29 |
Posh-Git에서 "git status"출력 색상 변경 (0) | 2020.11.29 |