47

비동기식 / 대기식에서 읽고 있었다.Task.Yield유용 할지도 모르고 다가왔다.이 게시물.나는 그 포스트에서 아래에 관한 질문이 있었다 :

async / await를 사용하면 해당 메소드가   기다릴 때 전화 해.FooAsync()실제로 비동기 적으로 실행됩니다.   내부 구현은 완전히 자유롭게 사용할 수 있습니다.   동기 경로.

내 머리 속에있는 비동기의 정의가 일렬로 정렬되어 있지 않기 때문에 아마도 이것은 나에게 약간 불분명하다.

내 마음 속에, 내가 주로 UI를 dev에 있기 때문에 비동기 코드는 UI 스레드에서 실행되지 않는 코드이지만 다른 스레드에서는 실행됩니다. 내가 인용 한 텍스트에서 알 수 있듯이, 메소드가 실제로는 비동기가 아닌 것은 모든 스레드에서 차단하는 경우 (예 : 스레드 풀 스레드 일지라도)입니다.

의문:

내가 CPU에 묶여있는 장기 실행 태스크를 가지고 있다면 (하드 한 수학을 많이 수행한다고 가정 해 봅시다), 비동기 적으로 태스크를 실행하면 일부 스레드가 올바르게 차단되어야합니다. 뭔가 실제로 수학을해야합니다. 기다리는 경우 일부 스레드가 차단됩니다.

진정으로 비동기식 메서드의 예는 무엇이며 어떻게 실제로 작동합니까? 그것들은 일부 하드웨어 기능을 활용하는 I / O 작업으로 제한되어있어 스레드가 차단되지 않습니다.


  • 필수 독서 과제 :blog.stephencleary.com/2013/11/there-is-no-thread.html - NWard
  • 귀하의 질문을 좋아하지만 제목 "Asynchronous methods in .Net / C #" 너무 일반적인 IMO입니다. - Julian
  • @Julian OP의 질문을보다 구체적으로 업데이트했습니다. - Brian Ogden
  • CPU가 바운드되면 CPU가 스레드에서 실행되고, 예 - 모든 코드가 스레드에서 실행됩니다. 네트워크에서 패킷을 기다리는 경우,그건 코드가 아니야.따라서 스레드에서 실행될 필요가 없습니다. (코드가 대기 시작하고 대기를 중지 할 수있는 코드가 있지만 실제 대기는 코드가 아닙니다.) - immibis

7 답변


105

내 머리 속에있는 비동기의 정의가 일렬로 정렬되어 있지 않기 때문에 아마도 이것은 나에게 약간 불분명하다.

설명을 요구해 주셔서 감사합니다.

내 마음 속에, 내가 주로 UI를 dev에 있기 때문에 비동기 코드는 UI 스레드에서 실행되지 않는 코드이지만 다른 스레드에서는 실행됩니다.

그 신념은 흔하지 만 틀린 것입니다. 비동기 코드가 두 번째 스레드에서 실행될 필요는 없습니다.

아침을 요리한다고 상상해보십시오. 토스터에 토스트를 몇 개 넣고 토스트가 터지기를 기다리는 동안 어제의 메일을보고 청구서를 지불하고 토스트가 튀어 나옵니다. 그 계산서 지불을 끝낸 다음 토스트 버터를 타십시오.

토스터를 보려고 두 번째 작업자를 고용 한 곳은 어디입니까?

너는하지 않았다. 스레드는 작업자입니다. 비동기 워크 플로는 모든 스레드에서 발생할 수 있습니다. 비동기 워크 플로의 요점은 다음과 같습니다.기피가능한 피할 수만 있다면 더 많은 근로자를 고용 할 수 있습니다.

내가 CPU에 묶여있는 장기 실행 태스크를 가지고 있다면 (하드 한 수학을 많이 수행한다고 가정 해 봅시다), 비동기 적으로 태스크를 실행하면 일부 스레드가 올바르게 차단되어야합니다. 뭔가 실제로 수학을해야합니다.

여기에서는 해결해야 할 어려운 문제를 제시 할 것입니다. 여기에 100 개의 숫자로 이루어진 열이 있습니다. 손으로 그들을 추가하십시오. 그래서 첫 번째를 두 번째에 추가하고 합계를 만듭니다. 그런 다음 누적 합계를 세 번째 합계에 더하고 합계를 얻습니다. 그럼, 아, 두 번째 페이지가 빠져 있습니다. 네가있는 곳을 기억하고 토스트를 만들어라. 아, 토스트가 토스트하는 동안, 나머지 숫자가있는 편지가 도착했습니다. 토스트를 끝내고 나면 그 숫자를 합산하고 계속해서 다음 번에 토스트를 먹는 것을 잊지 마십시오.

번호를 추가하기 위해 다른 직원을 고용 한 부분은 어디에 있습니까?계산 상 값 비싼 작업은 동기식 일 필요는 없으며 스레드를 차단할 필요가 없습니다.. 전산 작업을 잠재적으로 비동기로 만드는 것은 멈추는 능력, 당신이 어디에 있었는지 기억하고, 다른 일을하고, 무엇을해야하는지 기억할 수있는 능력입니다그 후, 그리고 그만 둔 곳에서 다시 시작하십시오.

이제는 확실히가능한숫자를 더하는 것 외에 아무 것도하지 않는 두 번째 작업자를 고용 한 다음 해고됩니다. 그리고 그 노동자에게 "너 끝났 니?"라고 물어볼 수있다. 대답이 '아니오'인 경우 완료 될 때까지 샌드위치를 만들 수 있습니다. 그렇게하면 너와 일하는 사람 모두 바쁘다. 그러나요건비동기는 여러 근로자를 포함합니다.

기다리는 경우 일부 스레드가 차단됩니다.

아니, 아니. 이것은 오해에서 가장 중요한 부분입니다.await"이 작업을 비동기 적으로 시작하십시오"라는 의미는 아닙니다.await"사용할 수없는 비동기식 결과가 여기에 있습니다. 사용할 수없는 경우,이 스레드에서 할 다른 작업을 찾으십시오.우리는아니스레드를 차단합니다. 기다리고있다.반대말네가 방금 한 말에

진정으로 비동기식 메서드의 예는 무엇이며 어떻게 실제로 작동합니까? 그것들은 일부 하드웨어 기능을 활용하는 I / O 작업으로 제한되어있어 스레드가 차단되지 않습니다.

비동기 작업에는 종종 사용자 지정 하드웨어 또는 여러 스레드가 포함되지만 필요하지는 않습니다.

생각하지 마.노동자. 생각 해봐.워크 플로우. 비동기의 본질은워크 플로우를 작은 부분으로 분해그렇게이러한 부분이 발생해야하는 순서를 결정할 수 있습니다, 그리고차례대로 각 부분을 실행, 그러나서로 종속되지 않는 파트가 인터리브되도록 허용.

비동기 워크 플로에서 쉽게 감지 할 수 있습니다.파트 간의 종속성이 표현되는 워크 플로의 장소. 이러한 부품에는await. 그것이await: 다음 코드는 완료되는 워크 플로 부분에 따라 달라 지므로 완료되지 않은 경우 수행 할 다른 작업을 찾아 나중에 작업이 완료 될 때 다시 돌아옵니다. 요점은 미래에 필요한 결과가 필요한 세계 에서조차 작업자를 계속 일하는 것입니다.


  • 이 스레드 추상화를 " 단지 일을하는 것 "이라는 개념보다 약간 재미있어합니다. (인터럽트 및 DPC와 같은), 지금 우리는 " 물건 만 다루기 "라는 추상화를 만들었습니다. 스레드를 기반으로합니다. - immibis
  • @immibis 인터럽트 및 DPC는 어떻게 추상화됩니까? 그것들은 추상화가 아닌 도구입니다.Task추상화입니다. - Euphoric
  • AFAIK JavaScript는 단일 스레드이며 비동기를 강조합니다. - Stephen S
  • @Euphoric 당신이 코멘트를 다시 읽고 싶을 수도 있습니다. 인터럽트와 DPC가 추상적이라고 말하지 않았습니다. - immibis

26

나는 비동기 / 기다리고있는 동안 읽고 있었다.

내 추천 해 주시겠습니까?async소개?

Task.Yield가 유용 할 때

거의 없다. 단위 테스트를 할 때 가끔 유용하다고 생각합니다.

내 마음 속에, 내가 주로 UI를 dev에 있기 때문에 비동기 코드는 UI 스레드에서 실행되지 않는 코드이지만 다른 스레드에서는 실행됩니다.

비동기 코드는 스레드리스 가능.

내가 인용 한 텍스트에서 알 수 있듯이, 메소드가 실제로는 비동기가 아닌 것은 모든 스레드에서 차단하는 경우 (예 : 스레드 풀 스레드 일지라도)입니다.

나는 그것이 옳다고 말할 것이다. 필자는 스레드를 차단하지 않는 (그리고 동기 적이 지 않은) 작업에 "진정한 비동기"라는 용어를 사용합니다. 나는 또한 "가짜 비동기"라는 용어를나타나다비동기이지만 스레드 풀 스레드에서 실행되거나 차단되기 때문에 작동합니다.

내가 CPU에 묶여있는 장기 실행 태스크를 가지고 있다면 (하드 한 수학을 많이 수행한다고 가정 해 봅시다), 비동기 적으로 태스크를 실행하면 일부 스레드가 올바르게 차단되어야합니다. 뭔가 실제로 수학을해야합니다.

예; 이 경우 동기화 된 API로 작업을 정의하고 동기 작업이므로 UI 스레드에서이 작업을 호출 할 수 있습니다.Task.Run예를 들어

var result = await Task.Run(() => MySynchronousCpuBoundCode());

기다리는 경우 일부 스레드가 차단됩니다.

아니; 스레드 풀 스레드는 코드를 실행하는 데 사용되며 (실제로 차단되지 않음) UI 스레드는 해당 코드가 완료 될 때까지 비동기 적으로 대기합니다 (차단되지도 않음).

진정으로 비동기식 메서드의 예는 무엇이며 어떻게 실제로 작동합니까?

NetworkStream.WriteAsync(간접적으로) 네트워크 카드에 몇 바이트를 쓰도록 요청합니다. 한 번에 하나씩 바이트를 쓰고 각 바이트가 쓰여지기를 기다리는 쓰레드는 없다. 네트워크 카드가 모든 것을 처리합니다. 네트워크 카드가 모든 바이트를 쓰는 것을 마쳤 으면, (결국) 반환 된 작업을 완료합니다.WriteAsync.

그것들은 일부 하드웨어 기능을 활용하는 I / O 작업으로 제한되어있어 스레드가 차단되지 않습니다.

I / O 작업이 쉬운 예이지만 전체적으로는 아닙니다. 타이머 (예 :Task.Delay). 모든 종류의 "이벤트"를 중심으로 진정한 비동기 API를 구축 할 수 있습니다.


  • 나는 Task.Yield가 기다리는 작업의 인라이닝을 효과적으로 막는 데 유용 할 수 있다고 생각한다. 현재 (async) 메소드가 반환 된 후에 기다린 작업을 확실히 실행하려면 기다린 작업 내에서 Task.Yield를 기다리면 대기중인 작업을 스케쥴러에서 더 아래로 밀어 넣을 수 있습니다 (즉, 인라인되지 않음). -- 예? - sjb-sjb
  • @ sjb-sjb :Task.Yield비동기를 강요합니다, 예. 그러나 언제 이것을해야합니까? 거의 없다. 단위 테스트를 할 때 때때로 유용합니다. 프로덕션 코드에서 필요하다면 프로덕션 코드의 디자인을 길고 꼼꼼히 살펴보고 개선 된 사항을 확인하십시오. - Stephen Cleary
  • 그래, 나는 일반적으로 동의하지만 여전히 일어난다. 나는 여기에서 설명하기에는 너무 복잡한 상황에서 최근에 마주 쳤지 만 디자인이 타당하다고 말하고 싶습니다. 일반적으로 Run을 사용하여 필요한 비동기를 가져올 수 있지만 현재 (UI) 스레드에서 수행해야하므로이 상황에서는이를 수행 할 수 없습니다. 또한 UI 스레드의 항목이 잘못된 순서로 발생하고 수익률을 사용하여 수정할 수있는 다른 상황을 보았습니다. - sjb-sjb
  • 첫 번째 예제는 현재 메서드가 종료 된 후에 UI 스레드에서 무언가를 실행하는 것입니다. 이를 위해 Window.Current.Dispatcher.RunAsync를 사용하는 또 다른 방법이 있습니다. 그러나 다른 예는 다음과 같습니다.이 메소드의 나머지가 실행되기 전에 UI 스레드에서 다른 것을 실행하게합니다 ("이 메소드 "는 UI 콜백 임). 그 때문에 Task.Yield는 별도의 메소드를 작성하고 Dispatcher.RunAsync를 사용하여 스케줄링 할 수 있지만 가장 쉬운 방법이라고 생각합니다. 그래서 네가 옳은 것 같아. Task.Yield를 생각한 경우 Dispatcher.RunAsync를 사용할 수 있습니다. - sjb-sjb
  • @ sjb-sjb :Task.Yield메시지 대기열이 Windows에서 작동하는 방식으로 인해 작동하지 않습니다. - Stephen Cleary

10

async / await를 사용하면 FooAsync ()를 기다릴 때 호출하는 메서드가 실제로 비동기 적으로 실행된다는 보장이 없습니다. 내부 구현은 완전히 동기식 경로를 사용하여 자유롭게 반환 할 수 있습니다.

이것은 아마도 나에게 약간 불분명합니다.   내 머리 속에 비동기가 늘어서 고 있지 않습니다.

이것은 단순히 비동기 메서드를 호출 할 때 두 가지 경우가 있음을 의미합니다.

첫 번째는 작업을 사용자에게 반환하면 작업이 이미 완료된 것입니다. 이는 동기식 경로가됩니다. 두 번째는 작업이 아직 진행 중이라는 것입니다.이 작업은 비동기 경로입니다.

이러한 경로를 모두 보여 주어야하는이 코드를 고려하십시오. 키가 캐시에 있으면 동기식으로 리턴됩니다. 그렇지 않으면 비동기 op가 시작되어 데이터베이스를 호출합니다.

Task<T> GetCachedDataAsync(string key)
{
    if(cache.TryGetvalue(key, out T value))
    {
        return Task.FromResult(value); // synchronous: no awaits here.
    }

    // start a fully async op.
    return GetDataImpl();

    async Task<T> GetDataImpl()
    {
        value = await database.GetValueAsync(key);
        cache[key] = value;
        return value;
    }
}

그래서 그것을 이해함으로써, 당신은 이론적으로database.GetValueAsync()유사한 코드를 가질 수 있으며 그 자체로 동기식으로 반환 할 수 있습니다.너의비동기 경로가 100 % 동 기적으로 실행될 수 있습니다. 하지만 코드에서 신경 쓸 필요는 없습니다. async / await는 두 경우 모두 완벽하게 처리합니다.

내가 CPU에 묶여있는 장기 실행 태스크를 가지고 있다면 (하드 한 수학을 많이 수행한다고 가정 해 봅시다), 비동기 적으로 태스크를 실행하면 일부 스레드가 올바르게 차단되어야합니다. 뭔가 실제로 수학을해야합니다. 기다리는 경우 일부 스레드가 차단됩니다.

블로킹은 잘 정의 된 용어입니다. 즉, 스레드가 무언가 (I / O, 뮤텍스 등)를 기다리는 동안 실행 창을 생성했음을 의미합니다. 따라서 수학을 수행하는 스레드는 차단 된 것으로 간주되지 않습니다. 실제로 작업을 수행하는 것입니다.

진정으로 비동기식 메서드의 예는 무엇이며 어떻게 실제로 작동합니까? 그것들은 일부 하드웨어 기능을 활용하는 I / O 작업으로 제한되어있어 스레드가 차단되지 않습니다.

"진정한 비동기 방법"은 단순히 차단하지 않는 방법입니다. 일반적으로 I / O를 포함하는 것으로 끝나지 만,await(UI 개발에서와 같이) 현재 스레드에서 다른 스레드를 원할 때 또는 병렬 처리를 도입하려고 할 때 무거운 수학 코드를 삽입하십시오.

async Task<double> DoSomethingAsync()
{
    double x = await ReadXFromFile();

    Task<double> a = LongMathCodeA(x);
    Task<double> b = LongMathCodeB(x);

    await Task.WhenAll(a, b);

    return a.Result + b.Result;
}


6

이 주제는 상당히 방대하며 여러 토론이 발생할 수 있습니다. 그러나,asyncawaitC #에서는 비동기 프로그래밍으로 간주됩니다. 그러나 비동기 작동 방식은 완전히 다른 논의입니다. 닷넷 4.5까지 비동기식 키워드는 없었고 개발자들은작업 병렬 라이브러리y (TPL). 개발자는 새 작업과 심지어는 스레드를 생성하는시기와 방법을 완전히 제어 할 수있었습니다. 그러나이 주제에 대해 실제로 전문가가 아니기 때문에 이것은 단점을 가지고 있었고 응용 프로그램은 스레드 간의 경쟁 조건으로 인한 성능 문제와 버그로 인해 어려움을 겪을 수있었습니다.

.NET 4.5부터는 비동기 프로그래밍에 대한 새로운 접근 방식으로 async 및 await 키워드가 도입되었습니다. async 및 await 키워드는 추가 스레드를 만들지 않습니다. 비동기 메서드는 자체 스레드에서 실행되지 않으므로 비동기 메서드는 다중 스레드가 필요하지 않습니다. 메서드는 현재 동기화 컨텍스트에서 실행되며 메서드가 활성화되어있을 때만 스레드에서 시간을 사용합니다. 당신이 사용할 수있는Task.RunCPU 바운드 작업을 백그라운드 스레드로 이동하지만 백그라운드 스레드는 결과가 사용 가능할 때까지 기다리는 프로세스에 도움이되지 않습니다.

비동기 프로그래밍에 대한 비동기 기반 접근법은 거의 모든 경우에서 기존 접근법보다 바람직합니다. 특히 코드가 더 간단하고 경쟁 조건을 지켜야 할 필요가 없기 때문에이 접근 방법은 IO 바인딩 작업에 대한 BackgroundWorker보다 낫습니다. 이 주제에 대해 더 많이 읽을 수 있습니다.이리.

나 자신을 C #블랙 벨트라고 생각하지는 않는다. 경험이 많은 개발자들도 몇 가지 더 토론을 할 수도 있지만 원칙적으로 나는 당신의 질문에 대답하기를 희망한다.


  • 유익한 내용이지만 OP의 질문에는 답할 수 없습니다. OP의 질문 인 IMO는 C #비동기 프로그래밍의 최근 히스토리 요약과 비동기식 C #비동기 프로그래밍의 MSDN에 대한 OP를 참조하는 연결이 OP가 찾는 대답이 아닐만큼 구체적입니다. 에 대한 그리고 나는 그것도 비동기 프로그래밍 비동기 프로그래밍 비동기 프로그래밍 C에서 MSDN을 읽은 후에도 여전히 OP 질문이 남아있을 것 같아요 생각 Logged - Brian Ogden
  • 비동기식 및 키워드 대기 중으로 인해 추가 스레드가 생성되지는 않지만 여전히 생각할 수있는 OP 질문에 완전히 답할 수 없다는 점을 지적한 것이 좋습니다. - Brian Ogden
  • 특정 방식으로, 나는 당신이 옳다고 생각합니다. 그러나 IMO는이 질문에 정말로 답을 할 수 없으며, 적어도 지금은 공식화 된 방식이 아닙니다. "무거운 수학"이란 무엇입니까? 의미는 모든 사람의 상상력에 주관적입니다. 대부분의 경우 비동기식 및 대기 형 작업을 수행하면 주 스레드를 차단하지 않으므로 응용 프로그램은 "대기"할 때까지 응답합니다. 결과. 의견을 보내 주셔서 감사합니다. 다음 번에 더 잘할 수 있기를 바랍니다 :) - Dan
  • OP 질문에 실제로 묻는 이유는 " FooAsync ()를 기다릴 때 호출하는 메서드가 실제로 비동기 적으로 실행된다는 보장은 없습니다. " OP가 "heavy math"를 사용하여 좋은 예를 제시하지 못할 수도 있지만 사실입니다. 대본 - Brian Ogden

6

비동기는 병렬을 의미하지 않습니다.

비동기는 동시성만을 의미합니다. 실제로 명시 적 스레드를 사용한다고해서 동시에 실행될 것이라고 보장 할 수는 없습니다 (예 : 스레드가 동일한 단일 코어에 대해 선호도가 있거나 일반적으로 시스템에 코어가 하나만있는 경우).

따라서 비동기 작업이 다른 작업과 동시에 발생할 것으로 예상해서는 안됩니다. 비동기식은 결국에는 일어날 것이라는 것을 의미합니다.다른 시간에(에이(그리스어) = without,가 SYN(그리스어) = 함께,크로노스(greek) = 시간. =>비동기식= 동시에 일어나지 않음).

주 : 비동기의 개념은 호출시 코드가 실제로 실행될 때 상관하지 않는다는 것입니다. 이를 통해 시스템은 가능한 경우 병렬 처리를 사용하여 작업을 실행합니다. 즉시 실행될 수도 있습니다. 심지어 같은 스레드에서 일어날 수도 있습니다 ... 나중에 더 많이.

때를await비동기 작업, 생성 중병행 성(~와 함께(라틴) = 함께,달리기(라틴어) = 실행. => "동시"=함께 달리기). 그 이유는 계속 진행하기 전에 비동기 작업이 완료 될 때까지 기다리고 있기 때문입니다. 실행이 수렴한다고 말할 수 있습니다. 이것은 스레드를 결합하는 개념과 유사합니다.


비동기가 병렬이 될 수없는 경우

async / await를 사용하면 FooAsync ()를 기다릴 때 호출하는 메서드가 실제로 비동기 적으로 실행된다는 보장이 없습니다. 내부 구현은 완전히 동기식 경로를 사용하여 자유롭게 반환 할 수 있습니다.

이것은 세 가지 방법으로 발생할 수 있습니다.

  1. 사용할 수 있습니다.await돌아 오는 모든 것에Task. 귀하가Task그것은 이미 완료되었을 수 있습니다.

    그러나 이것만으로도 동 기적으로 작동한다는 것을 의미하지는 않습니다. 사실, 그것은 비동기 적으로 실행되었고 당신이Task예.

    당신이 할 수있는 것을 명심하십시오.await이미 완료된 작업 :

    private static async Task CallFooAsync()
    {
        await FooAsync();
    }
    
    private static Task FooAsync()
    {
        return Task.CompletedTask;
    }
    
    private static void Main()
    {
        CallFooAsync().Wait();
    }
    

    또한,async메소드에는 없음await그것은 동 기적으로 실행됩니다.

    참고 : 이미 알고있는 것처럼,Task네트워크 나 파일 시스템 등에서 대기 중일 수 있습니다. 그렇게하면 새로운Thread또는ThreadPool.

  2. 단일 스레드에서 처리하는 동기화 컨텍스트에서 결과는 다음과 같이 실행됩니다.Task동기식으로, 약간의 오버 헤드가있다. UI 스레드의 경우입니다. 아래에서 어떤 일이 발생하는지 자세히 설명합니다.

  3. 사용자 정의를 작성할 수 있습니다.작업 스케줄러항상 동기식으로 작업을 실행합니다. 동일한 스레드에서 호출이 수행됩니다.

    참고 : 최근에 나는 커스텀을 썼다.SyncrhonizationContext단일 스레드에서 작업을 실행합니다. 에서 찾을 수 있습니다.작업 스케줄러 (System.Threading.Tasks.) 만들기. 그것은 그러한 결과를 낳을 것이다.TaskScheduler~에 전화를했다.FromCurrentSynchronizationContext.

    기본값은TaskScheduler호출을 호출에 대기열에 넣습니다.ThreadPool. 그러나 수술을 기다릴 때, 수술을받지 않으면ThreadPool그것을 제거하려고 시도합니다.ThreadPool대기중인 동일한 스레드에서 인라인으로 실행하십시오. 스레드가 대기 중이므로 사용 중이 지 않습니다.

    참고 : 주목할만한 예외 중 하나는Task표시가있는LongRunning.LongRunning Task별도의 스레드에서 실행됩니다..


귀하의 질문

내가 CPU에 묶여있는 장기 실행 태스크를 가지고 있다면 (하드 한 수학을 많이 수행한다고 가정 해 봅시다), 비동기 적으로 태스크를 실행하면 일부 스레드가 올바르게 차단되어야합니다. 뭔가 실제로 수학을해야합니다. 기다리는 경우 일부 스레드가 차단됩니다.

계산을 수행하는 경우 해당 부분이 올바른 스레드에서 발생해야합니다.

그럼에도 불구하고asyncawait대기중인 스레드를 차단할 필요가 없다는 것입니다 (나중에 자세히 설명합니다). 그러나 대기중인 동일한 스레드에서 실행되도록 예정된 작업을 예약하여 동기 실행 (UI 스레드에서 실수로 쉽게 발생하는)으로 인해 발을 쏘는 것은 매우 쉽습니다.

의 주요 특징 중 하나asyncawait그들은SynchronizationContext발신자에게서. 기본 스레드를 사용하는 대부분의 스레드TaskScheduler(앞서 언급했듯이,ThreasPool). 그러나 UI 스레드의 경우 메시지 큐에 작업을 게시한다는 의미입니다. 즉, UI 스레드에서 실행됩니다. 이것의 장점은 당신이 사용하지 않아도된다는 것입니다.Invoke또는BeginInvokeUI 구성 요소에 액세스합니다.

방법에 들어가기 전에await에이TaskUI 스레드에서 그것을 차단하지 않고, 나는 그것을 구현하는 것이 가능하다는 것을 알고 싶다.TaskScheduler당신이await~에Task, 당신은 당신의 스레드를 막지 않거나 유휴 상태가되지 않게합니다.Task실행을 기다리는 중입니다.내가 그랬을 때.NET 2.0에 대한 백 포스팅 작업나는 이것을 실험했다.

진정으로 비동기식 메서드의 예는 무엇이며 어떻게 실제로 작동합니까? 그것들은 일부 하드웨어 기능을 활용하는 I / O 작업으로 제한되어있어 스레드가 차단되지 않습니다.

너는 혼란스러워 보인다.비동기스레드를 차단하지 않음. .NET에서 스레드를 차단할 필요가없는 비동기 작업의 예가 무엇인지 알고 싶다면 쉽게 이해할 수있는 방법을 사용하는 것이 좋습니다.계속대신에await. UI 스레드에서 실행해야하는 계속 작업에 대해서는 다음을 사용할 수 있습니다.TaskScheduler.FromCurrentSynchronizationContext.

멋진 스핀 대기를 구현하지 마십시오.. 그리고 그 말은Timer,Application.Idle또는 그런 것.

사용하면async컴파일러에게 메소드의 코드를 깨뜨리는 방식으로 코드를 다시 작성하라고 말하고 있습니다. 결과는 훨씬 더 편리한 구문으로 계속과 유사합니다. 스레드가await그만큼Task이 스레드는 예약되고 스레드는 현재 이후에도 계속 진행할 수 있습니다.async호출 (메소드 밖). 때Task완료되면, 계속 (await) 계획되었다.

UI 스레드의 경우 이것은 일단 도달하면await메시지를 계속 처리 할 수 있습니다. 기다려지면Task완료되면, 계속 (await)이 예정됩니다. 결과적으로 도달await실을 막는 것을 의미하지는 않습니다.

그러나 맹목적으로 추가asyncawait모든 문제를 해결할 수는 없습니다.

나는 너에게 실험을한다. 새로운 Windows Forms 응용 프로그램을 만들고ButtonTextBox, 다음 코드를 추가하십시오.

    private async void button1_Click(object sender, EventArgs e)
    {
        await WorkAsync(5000);
        textBox1.Text = @"DONE";
    }

    private async Task WorkAsync(int milliseconds)
    {
        Thread.Sleep(milliseconds);
    }

UI를 차단합니다. 앞에서 언급했듯이,await자동으로SynchronizationContext호출자 스레드의 이 경우 UI 스레드입니다. 따라서,WorkAsyncUI 스레드에서 실행됩니다.

다음과 같이됩니다.

  • UI 스레드는 클릭 메시지를 가져 와서 클릭 이벤트 핸들러를 호출합니다.
  • 클릭 이벤트 핸들러에서 UI 스레드가 도달합니다.await WorkAsync(5000)
  • WorkAsync(5000)(그리고 그 연속을 스케쥴링하는) UI 스레드 동기화 컨텍스트 인 현재 동기화 컨텍스트에서 실행되도록 스케줄됩니다 ... 이것은 메시지를 게시하는 것을 의미합니다
  • UI 스레드가 이제 더 이상의 메시지를 처리 할 수 있습니다.
  • UI 스레드는 실행할 메시지를 선택합니다.WorkAsync(5000)그것의 계속을 계획하고
  • UI 스레드 호출WorkAsync(5000)계속과 함께
  • 에서WorkAsync, UI 스레드가 실행됩니다.Thread.Sleep. 이제 UI가 5 초 동안 응답하지 않습니다.
  • 계속 작업은 나머지 클릭 이벤트 처리기가 실행되도록 예약합니다. 이는 UI 스레드에 대한 다른 메시지를 게시하여 수행됩니다
  • UI 스레드가 이제 더 이상의 메시지를 처리 할 수 있습니다.
  • UI 스레드는 클릭 이벤트 핸들러에서 계속하기 위해 메시지를 선택합니다.
  • UI 스레드가 텍스트 상자를 업데이트합니다.

결과는 오버 헤드가있는 동기 실행입니다.

예, 다음을 사용해야합니다.Task.Delay대신. 그것은 요점이 아닙니다. 중히 여기다Sleep어떤 계산을 위해서야. 요점은asyncawait어디서나 자동으로 병렬 응용 프로그램을 제공하지 않습니다. 백그라운드 스레드에서 무엇을 실행할지 선택하는 것이 좋습니다 (예 :ThreadPool) 그리고 당신은 UI 스레드에서 실행하고 싶습니다.

이제 다음 코드를 시도해보십시오.

    private async void button1_Click(object sender, EventArgs e)
    {
        await Task.Run(() => Work(5000));
        textBox1.Text = @"DONE";
    }

    private void Work(int milliseconds)
    {
        Thread.Sleep(milliseconds);
    }

UI를 차단하지 않는다는 것을 알게 될 것입니다. 이것은이 경우Thread.Sleep이제ThreadPool덕분에Task.Run. 고마워.button1_Click존재async, 일단 코드가 도달하면awaitUI 스레드는 자유롭게 작업을 계속할 수 있습니다. 후Task코드가 완료되면 코드가 다시 시작됩니다.await컴파일러가 정확한 방법으로 다시 작성해 주셔서 감사합니다.

다음과 같이됩니다.

  • UI 스레드는 클릭 메시지를 가져 와서 클릭 이벤트 핸들러를 호출합니다.
  • 클릭 이벤트 핸들러에서 UI 스레드가 도달합니다.await Task.Run(() => Work(5000))
  • Task.Run(() => Work(5000))(그리고 그 연속을 스케쥴링하는) UI 스레드 동기화 컨텍스트 인 현재 동기화 컨텍스트에서 실행되도록 스케줄됩니다 ... 이것은 메시지를 게시하는 것을 의미합니다
  • UI 스레드가 이제 더 이상의 메시지를 처리 할 수 있습니다.
  • UI 스레드는 실행할 메시지를 선택합니다.Task.Run(() => Work(5000))완료되면 계속할 수 있습니다.
  • UI 스레드 호출Task.Run(() => Work(5000))계속 진행하면ThreadPool
  • UI 스레드가 이제 더 이상의 메시지를 처리 할 수 있습니다.

ThreadPool완료되면 나머지는 클릭 이벤트 처리기의 나머지 일정을 실행하도록 예약합니다. 이는 UI 스레드에 대한 다른 메시지를 게시하여 수행됩니다. UI 스레드가 클릭 이벤트 핸들러에서 계속하기 위해 메시지를 선택하면 텍스트 상자가 업데이트됩니다.


  • "a" ' 그리스 ' 없이. 라틴어가 아닙니다.) - Thanasis Ioannidis
  • @ ThanasisIoannidis가 수정되었습니다. - Theraot

4

스티븐의 대답은 이미 위대하다. 그래서 나는 그가 말한 것을 반복하지 않을 것이다. 나는 Stack Overflow (와 다른 곳에서)에 대해 여러 번 같은 논쟁을 반복하는 데 공감했다.

대신, 비동기 코드에 대한 중요한 추상적 인 사항 중 하나에 집중하겠습니다 : 절대 한정자가 아닙니다. 코드 조각이 비동기라고 말하는 것은 의미가 없습니다. 항상 비동기입니다.다른 것과 관련하여. 이것은 매우 중요합니다.

목적await비동기 작업과 동기 코드 연결 중 일부에 동기 워크 플로를 구축하는 것입니다. 귀하의 코드등장하다완벽한 동기1코드 자체.

var a = await A();
await B(a);

이벤트의 순서는await호출. B는 A의 반환 값을 사용합니다. 이는 A가 B보다 먼저 실행되어야 함을 의미합니다.이 코드가 포함 된 메서드는 동기 워크 플로를 가지며 두 메서드 A와 B는 서로 동기식입니다.

이것은 동기식 워크 플로우가 일반적으로 생각하기 쉽고, 더 중요한 것은 많은 워크 플로우를 간단히 생각할 수 있기 때문에 매우 유용합니다.아르동기. B가 A를 실행해야하는 결과가 필요한 경우절대로 필요한 것A 후에 달린다.2. 다른 HTTP 요청의 URL을 얻기 위해 HTTP 요청을해야하는 경우 첫 번째 요청이 완료 될 때까지 기다려야합니다. 스레드 / 작업 스케줄링과는 아무런 관련이 없습니다. 아마도 우리는 주문할 필요가없는 것들에 대한 명령을 강요하는 "우발적 인 동시성"과 별개로이 "고유의 동시성"이라고 부를 수 있습니다.

너는 이렇게 말한다 :

내 마음 속에, 내가 주로 UI를 dev에 있기 때문에 비동기 코드는 UI 스레드에서 실행되지 않는 코드이지만 다른 스레드에서는 실행됩니다.

UI와 관련하여 비동기 적으로 실행되는 코드를 설명하고 있습니다. 비동기 (사람들은 응답을 멈추는 UI를 좋아하지 않습니다)에 대해서는 매우 유용한 경우입니다. 그러나 그것은보다 일반적인 원칙의 특정 사례 일뿐입니다. 서로 관련하여 일이 순조롭게 진행되도록 허용합니다. 다시 말하지만 절대적인 것은 아닙니다.약간(즉, 사용자가 창을 끌거나 진행 막대가 변경되면 창은 다시 그려야합니다.)아니(Load 액션이 끝나기 전에 Process 버튼을 클릭해서는 안됩니다.)await이 유스 케이스에서는 그렇지 않다.사용하는 것과는 다른Application.DoEvents원칙적으로 동일한 문제와 이점을 많이 소개합니다.

이것은 원래 인용구가 흥미로운 부분이기도합니다. UI는 스레드를 업데이트해야합니다. 이 스레드는 이벤트 처리기를 호출합니다.await. 선이 어디에 있는지 의미합니까?await사용자 입력에 응답하여 UI가 업데이트되도록 허용 할 것인가? 아니.

먼저,await메서드 호출과 마찬가지로 인수를 사용합니다. 내 견본에서,A에 의해 생성 된 코드보다 먼저 호출되어야합니다.await"UI 루프로 제어 해제"를 포함한 모든 작업을 수행 할 수 있습니다. 반환 값A~이다.Task<T>단지 대신T"미래의 가능한 가치"를 나타내는await생성 된 코드는 값이 이미 있는지 (동일한 스레드에서 계속 진행되는 경우) 또는 없는지 (즉, 스레드를 UI 루프로 다시 릴리스한다는 의미)를 확인합니다. 그러나 두 경우 모두Task<T>가치 그 자체절대로 필요한 것에서 돌아왔다.A.

이 구현을 고려하십시오.

public async Task<int> A()
{
  Thread.Sleep(1000);

  return 42;
}

발신자 필요A값을 돌려주는 (int의 태스크). 거기에 아무 것도 없기 때문에await이 방법에서return 42;. 그러나 수면이 완료되기 전에는 두 작업이 스레드에 대해 동기 적이기 때문에이 작업을 수행 할 수 없습니다. 호출자 스레드는 사용 여부에 관계없이 잠시 차단됩니다.await또는 아닙니다 - 차단이 실행 중입니다.A()그 자체가 아니라await theTaskResultOfA.

이와 대조적으로 다음을 고려하십시오.

public async Task<int> A()
{
  await Task.Delay(1000);

  return 42;
}

실행이await, 그것은 기다리고있는 작업이 아직 끝나지 않았으며 호출자에게 다시 제어권을 돌려 준다. 그리고await그러면 호출자의 컨트롤이그...방문객. 우리는 UI와 관련하여 코드의 일부를 비동기로 만들었습니다. UI 스레드와 A 사이의 동시성은 우발적이었으며이를 제거했습니다.

여기서 중요한 부분은 코드를 검사하지 않고 두 구현을 외부에서 구별 할 수있는 방법이 없다는 것입니다. 반환 형식 만 메서드 시그니처의 일부입니다. 메서드가 비동기 적으로 실행됩니다.할 수있다. 이것은 여러 가지 좋은 이유가있을 수 있습니다. 따라서 싸울만한 의미가 없습니다. 예를 들어, 결과가 이미 사용 가능할 때 실행 스레드를 깨뜨리지는 못합니다.

var responseTask = GetAsync("http://www.google.com");

// Do some CPU intensive task
ComputeAllTheFuzz();

response = await responseTask;

우리는 어떤 일을해야합니다. 일부 이벤트는 다른 이벤트와 비동기 적으로 실행될 수 있습니다 (이 경우,ComputeAllTheFuzzHTTP 요청과 독립적입니다) 비동기입니다. 그러나 어느 시점에서 우리는 동기식 워크 플로우로 돌아갈 필요가 있습니다 (예를 들어,ComputeAllTheFuzz및 HTTP 요청). 그게 바로await포인트, 다시 실행을 동기화합니다 (여러 비동기 워크 플로우가 있다면Task.WhenAll). 그러나 HTTP 요청이 계산 전에 완료되면 관리 요청을 처리 할 시점이 없습니다.awaitpoint - 같은 스레드에서 계속 진행할 수 있습니다. CPU 낭비가 없습니다. 스레드를 차단하지 않습니다. 유용한 CPU 작업을 수행합니다. 그러나 UI를 업데이트 할 수있는 기회는 없었습니다.

물론 이것은 일반적으로보다 일반적인 비동기 메소드에서 피할 수있는 이유입니다. 이는 비동기 코드의 일부 용도 (스레드 및 CPU 시간 낭비를 피함)에 유용하지만 다른 것은 사용하지 않습니다 (UI를 응답 성있게 유지). 이러한 방식으로 UI를 반응 적으로 유지하려는 경우 결과에 만족하지 않을 것입니다. 그러나 웹 서비스의 일부로 사용하는 경우, 예를 들어 위대한 기능을 수행 할 수 있습니다. 즉, 스레드 낭비를 피하고 UI 응답을 유지하지 않는 것입니다 (서비스 끝점을 비동기 적으로 호출하여 제공됨). 다시 서비스 측면에서 같은 일).

요컨대,await호출자와 관련하여 비동기적인 코드를 작성할 수 있습니다. 그것은 비동기 성의 마법의 힘을 불러 일으키지 않으며, 모든 것에 비동기 적이 지 않으며, CPU 사용이나 스레드 차단을 방해하지도 않습니다. 비동기 작업에서 동기 워크 플로를 쉽게 만들 수있는 도구를 제공하고 호출자와 관련하여 전체 워크 플로의 일부를 비동기로 제시합니다.

UI 이벤트 핸들러를 살펴 보겠습니다. 개별 비동기 작업에서 스레드를 실행할 필요가없는 경우 (예 : 비동기 I / O) 비동기 메서드의 일부가 원래 스레드에서 다른 코드를 실행할 수 있습니다. UI는 해당 부분에서 응답합니다. 작업이 CPU / 스레드를 다시 필요로 할 때,기발한스레드가 작업을 계속할 수 있습니다. 이 경우 UI는 CPU 작업 기간 동안 다시 차단됩니다. 그렇지 않은 경우 (웨이터이것을 사용하여 지정합니다.ConfigureAwait(false)), UI 코드는 병렬로 실행됩니다. 물론 둘 다 처리 할 수있는 충분한 리소스가 있다고 가정합니다. 항상 응답 성을 유지하기 위해 UI가 필요하다면 UI 스레드를 눈에 띄게 오래 실행할 수는 없습니다. 즉, 일반적으로 비동기식이지만 때로는 몇 초 동안 블록을 비동기로 처리해야한다는 의미이기도합니다. 비동기식 방법Task.Run. 두 방법 모두 비용과 이점이 있습니다. 모든 엔지니어링과 마찬가지로 트레이드 오프입니다. :)


  1. 물론 추상화가 완벽 할 때까지 완벽합니다. 추상화가 누출 될 때마다 기다리고있는 많은 누수와 비동기 실행에 대한 다른 접근법이 있습니다.
  2. 충분히 똑똑한 옵티 마이저는 A의 리턴 값이 실제로 필요한 지점까지 B의 일부분을 실행할 수 있습니다. 이것은 CPU가 정상 "동기"코드로 수행하는 것입니다 (순서가 맞지 않는 실행). 이러한 최적화절대로 필요한 것그러나 CPU가 연산 순서를 잘못 판단하면 결과를 무시하고 올바른 순서를 제시해야합니다.


3

async / aswait을 사용하여 코드가 제어를 차단하고 다른 흐름으로 제어를 해제 한 다음 제어를 재개하지만 스레드가 필요없는 방법을 보여주는 비동기 코드가 있습니다.

public static async Task<string> Foo()
{
    Console.WriteLine("In Foo");
    await Task.Yield();
    Console.WriteLine("I'm Back");
    return "Foo";
}


static void Main(string[] args)
{
    var t = new Task(async () =>
    {
        Console.WriteLine("Start");
        var f = Foo();
        Console.WriteLine("After Foo");        
        var r = await f;
        Console.WriteLine(r);
    });
    t.RunSynchronously();
    Console.ReadLine();
}

enter image description here

따라서 async / await에서 핵심적인 결과를 원할 때 제어 및 재 동기화를 릴리스하는 것입니다 (스레딩과 잘 작동 함)

참고 :이 코드를 만들 때 스레드가 차단되지 않았습니다. :)

나는 때로는 혼란이 "Tasks"에서 나올 수도 있다고 생각하는데, 이것은 자신의 스레드에서 동작하는 것을 의미하지는 않는다. 비동기 / 대기는 작업을 여러 단계로 나누고 여러 단계를 하나의 흐름으로 조정할 수있게 해줍니다.

요리와 비슷합니다. 조리법을 따르십시오. 요리 준비를하기 전에 모든 준비 작업을해야합니다. 그래서 당신은 오븐을 켜고, 물건을 자르고, 물건 등을 그려 넣습니다. 그런 다음 오븐의 온도를 기다리고 준비 작업을 기다립니다. 논리적 (작업 / 비동기 / 대기) 인 것처럼 보이는 방식으로 작업 사이를 혼자서 바꿀 수는 있지만 당근 (스레드)을 잘라내는 동안 다른 사람이 치즈를 마실 때 도움이 될 수 있습니다.

연결된 질문


관련된 질문

최근 질문