Task.Start / Wait와 Async / Await의 차이점은 무엇입니까?


206

뭔가 빠졌을 수도 있지만 차이점은 무엇입니까?

public void MyMethod()
{
  Task t = Task.Factory.StartNew(DoSomethingThatTakesTime);
  t.Wait();
  UpdateLabelToSayItsComplete();
}

public async void MyMethod()
{
  var result = Task.Factory.StartNew(DoSomethingThatTakesTime);
  await result;
  UpdateLabelToSayItsComplete();
}

private void DoSomethingThatTakesTime()
{
  Thread.Sleep(10000);
}

답변:


395

뭔가 빠졌을 수도 있습니다

너는.

Task.Wait과 의 차이점은 무엇 await task입니까?

식당에서 웨이터에게 점심을 주문합니다. 당신의 명령을 한 순간, 친구가 들어와 당신 옆에 앉아 대화를 시작합니다. 이제 두 가지 선택이 있습니다. 작업이 완료 될 때까지 친구를 무시할 수 있습니다. 수프가 도착할 때까지 기다렸다가 기다리는 동안 아무 것도 할 수 없습니다. 또는 친구에게 응답 할 수 있으며 친구가 말을 멈 추면 웨이터가 수프를 가져옵니다.

Task.Wait작업이 완료 될 때까지 차단-작업이 완료 될 때까지 친구를 무시합니다. await메시지 대기열에서 메시지 처리를 유지하고 작업이 완료되면 "대기 후 중단 한 부분을 선택합니다"라는 메시지를 대기열에 넣습니다. 친구와 대화를 나누고 대화가 중단되면 수프가 도착합니다.


5
@ronag 아니요, 그렇지 않습니다. Task10ms가 걸리는 것을 기다리는 것이 실제로 Task스레드에서 10 시간 동안 실행되어 10 시간 동안 전체를 차단하는 경우 어떻게 하시겠습니까?
svick

62
@StrugglingCoder 다음 await를 운영자가하지 않습니다 제외하고 아무것도 피연산자를 평가하고 즉시 현재 호출에 작업을 반환합니다 . 사람들은이 아이디어를 머리에 꿰매어 작업을 스레드로 오프로드해야만 달성 할 수 있다는 사실을 알게되었지만 사실은 아닙니다. 토스트가 토스터에있는 동안 요리사가 토스터를 보지 않고도 아침 식사를 요리하고 신문을 읽을 수 있습니다. 사람들은 토스터 안에 숨겨져있는 실 (작업자)이 있어야한다고 말하지만, 토스터를 보면 토스트를보고있는 사람이 거의 없다고 확신합니다.
Eric Lippert

11
@StrugglingCoder : 그렇다면 누가 작업을하고 있습니까? 다른 스레드가 작업을 수행 중이고 해당 스레드가 CPU에 할당되어 작업이 실제로 수행되고 있습니다. 어쩌면 작업이 하드웨어에 의해 수행되고 있으며 전혀 스레드가 없습니다. 그러나 분명히 하드웨어에는 스레드 가 있어야합니다 . 아니요. 하드웨어는 스레드 수준 아래에 있습니다. 실이 필요 없습니다! Stephen Cleary의 기사 없음 스레드를 읽으면 도움이 될 수 있습니다.
Eric Lippert

6
@StrugglingCoder : 이제, 비동기 작업이 수행되고 하드웨어가없고 다른 스레드가 없다고 가정하십시오. 이것이 어떻게 가능한지? 글쎄, 당신이 기다린 것이 일련의 Windows 메시지를 대기열에 넣었다고 가정하십시오 . 각 메시지 는 약간의 작업을 수행합니까? 이제 어떻게됩니까? 메시지 루프로 제어를 되돌리고, 큐에서 메시지를 가져 오기 시작하고 매번 약간의 작업을 수행하며 마지막으로 수행 된 작업은 "작업의 연속 실행"입니다. 여분의 스레드가 없습니다!
Eric Lippert

8
@StrugglingCoder : 이제 방금 말한 것을 생각해보십시오. 이미 Windows가 작동하는 방식임을 알고 있습니다 . 일련의 마우스 움직임과 버튼 클릭 등을 실행합니다. 메시지는 대기열에 들어가고 차례로 처리되며 각 메시지는 소량의 작업을 수행하며 모든 작업이 완료되면 시스템은 계속 작동합니다. 하나의 스레드에서 비동기는 큰 작업을 작은 비트로 나누고 대기열에 넣고 모든 작은 비트를 순서대로 실행하는 것입니다. 이러한 실행 중 일부는 다른 작업이 대기열에 들어가 수명을 연장시킵니다. 실 하나!
Eric Lippert

121

여기에 Eric의 대답을 보여주는 코드가 있습니다.

public void ButtonClick(object sender, EventArgs e)
{
  Task t = new Task.Factory.StartNew(DoSomethingThatTakesTime);
  t.Wait();  
  //If you press Button2 now you won't see anything in the console 
  //until this task is complete and then the label will be updated!
  UpdateLabelToSayItsComplete();
}

public async void ButtonClick(object sender, EventArgs e)
{
  var result = Task.Factory.StartNew(DoSomethingThatTakesTime);
  await result;
  //If you press Button2 now you will see stuff in the console and 
  //when the long method returns it will update the label!
  UpdateLabelToSayItsComplete();
}

public void Button_2_Click(object sender, EventArgs e)
{
  Console.WriteLine("Button 2 Clicked");
}

private void DoSomethingThatTakesTime()
{
  Thread.Sleep(10000);
}

27
코드에 +1 (백 번 읽는 것보다 한 번 실행하는 것이 좋습니다). 그러나 " //If you press Button2 now you won't see anything in the console until this task is complete and then the label will be updated!" 문구 가 잘못되었습니다. 와 버튼을 눌러시 t.Wait();버튼 클릭 이벤트 핸들러에서 ButtonClick()이 보도 아무것도 할 수 없습니다와 GUI는 GUI와 클릭이나 상호 작용입니다, 냉동 및 응답이기 때문에 "이 작업이 완료 될 때까지"다음 콘솔과 라벨의 갱신에 무언가를보고 되는 손실 작업 대기의 완료 될 때까지
나디 Vanin Геннадий Ванин에게

2
Eric은 귀하가 Task API에 대한 기본적인 이해가 있다고 가정합니다. 나는 그 코드를보고 " t.Wait작업이 완료 될 때까지 메인 스레드를 차단할 것 "이라고 스스로에게 말합니다 .
머핀 맨

50

이 예는 그 차이를 매우 명확하게 보여줍니다. async / await를 사용하면 호출 스레드가 차단되지 않고 계속 실행됩니다.

static void Main(string[] args)
{
    WriteOutput("Program Begin");
    // DoAsTask();
    DoAsAsync();
    WriteOutput("Program End");
    Console.ReadLine();
}

static void DoAsTask()
{
    WriteOutput("1 - Starting");
    var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime);
    WriteOutput("2 - Task started");
    t.Wait();
    WriteOutput("3 - Task completed with result: " + t.Result);
}

static async Task DoAsAsync()
{
    WriteOutput("1 - Starting");
    var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime);
    WriteOutput("2 - Task started");
    var result = await t;
    WriteOutput("3 - Task completed with result: " + result);
}

static int DoSomethingThatTakesTime()
{
    WriteOutput("A - Started something");
    Thread.Sleep(1000);
    WriteOutput("B - Completed something");
    return 123;
}

static void WriteOutput(string message)
{
    Console.WriteLine("[{0}] {1}", Thread.CurrentThread.ManagedThreadId, message);
}

DoAsTask 출력 :

[1] 프로그램 시작
[1] 1-시작
[1] 2-작업 시작
[3] A-시작한 것
[3] B-무언가를 완성 함
[1] 3-작업 완료 결과 : 123
[1] 프로그램 끝

DoAsAsync 출력 :

[1] 프로그램 시작
[1] 1-시작
[1] 2-작업 시작
[3] A-시작한 것
[1] 프로그램 끝
[3] B-무언가를 완성 함
[3] 3-작업 완료 결과 : 123

업데이트 : 출력에 스레드 ID를 표시하여 예제를 개선했습니다.


4
그러나 내가 할 경우 : new Task (DoAsTask) .Start (); DoAsAsync () 대신; 나는 같은 기능을 얻을 수 있으므로 어디에서 이점을 얻을 수
있을까

1
당신의 제안으로, 작업의 결과는 다른 곳, 다른 방법이나 람다로 평가되어야합니다. async-await는 비동기 코드를보다 쉽게 ​​추적 할 수 있도록합니다. 그것은 단지 구문 향상 기입니다.
Mas

@Mas 왜 프로그램 끝이 A 이후에 시작했는지 모르겠습니다. 키워드 프로세스를 기다리는 순간 내 이해에서 즉시 주요 컨텍스트로 이동 한 다음 되돌아 가야합니다.

@JimmyJimm 내 이해에서 Task.Factory.StartNew는 DoSomethingThatTakesTime을 실행하기 위해 새 스레드를 가동시킵니다. 따라서 Program End 또는 A-Started Something이 먼저 실행 될지 여부는 보장되지 않습니다.
RiaanDP

@ JimimJimm : 스레드 ID를 표시하도록 샘플을 업데이트했습니다. 보다시피, "Program End"와 "A-Started something"은 다른 스레드에서 실행되고 있습니다. 따라서 실제로 순서는 결정적이지 않습니다.
Mas

10

Wait ()는 잠재적으로 비동기 코드를 동기화 방식으로 실행합니다. 기다리지 않습니다.

예를 들어 asp.net 웹 응용 프로그램이 있습니다. UserA는 / getUser / 1 엔드 포인트를 호출합니다. asp.net 앱 풀은 스레드 풀 (Thread1)에서 스레드를 선택하고이 스레드는 http 호출을 수행합니다. Wait ()를 수행하면 http 호출이 해결 될 때까지이 스레드가 차단됩니다. 기다리는 동안 UserB가 / getUser / 2를 호출하면 앱 풀은 다른 스레드 (Thread2)를 제공하여 http를 다시 호출해야합니다. Thread1을 사용할 수 없으므로 Wait ()에 의해 차단 되었기 때문에 방금 다른 스레드를 만들었습니다 (실제로 앱 풀에서 가져 왔습니다).

Thread1에서 await를 사용하면 SyncContext가 Thread1과 http 호출 간의 동기화를 관리합니다. 간단히 말하면 http 호출이 완료되면 알립니다. 한편 UserB가 / getUser / 2를 호출하면 Thread1을 다시 사용하여 http 호출이 이루어집니다. 그런 다음 다른 요청이 더 많은 것을 사용할 수 있습니다. http 호출이 완료되면 (user1 또는 user2) Thread1은 결과를 가져 와서 호출자 (클라이언트)에게 리턴 할 수 있습니다. Thread1은 여러 작업에 사용되었습니다.


9

이 예에서는 실용적이지 않습니다. 다른 스레드 (예 : WCF 호출)를 반환하거나 파일 IO와 같은 운영 체제에 대한 제어 권한을 포기하는 작업을 기다리는 경우 대기는 스레드를 차단하지 않아 시스템 리소스를 덜 사용합니다.


3

위의 예에서 "TaskCreationOptions.HideScheduler"를 사용하고 "DoAsTask"메소드를 크게 수정할 수 있습니다. "작업"값을 반환하고 "비동기"로 표시되어 여러 조합을 수행하기 때문에 "DoAsAsync"에서 발생하므로 메소드 자체는 비동기식이 아닙니다. "async / await"를 사용하는 것과 정확히 같은 방식입니다. :

static Task DoAsTask()
{
    WriteOutput("1 - Starting");
    var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime, TaskCreationOptions.HideScheduler); //<-- HideScheduler do the magic

    TaskCompletionSource<int> tsc = new TaskCompletionSource<int>();
    t.ContinueWith(tsk => tsc.TrySetResult(tsk.Result)); //<-- Set the result to the created Task

    WriteOutput("2 - Task started");

    tsc.Task.ContinueWith(tsk => WriteOutput("3 - Task completed with result: " + tsk.Result)); //<-- Complete the Task
    return tsc.Task;
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.