Task.Run을 비 동기화하는 방법으로 넣어야합니까?


304

가장 간단한 형태로 비동기 대기를 이해하려고합니다. 이 예제를 위해 두 개의 숫자를 더하는 매우 간단한 방법을 만들고 싶습니다. 처리 시간이 전혀 없으며 여기서 예제를 작성하는 것입니다.

실시 예 1

private async Task DoWork1Async()
{
    int result = 1 + 2;
}

실시 예 2

private async Task DoWork2Async()
{
    Task.Run( () =>
    {
        int result = 1 + 2;
    });
}

기다리는 경우 DoWork1Async()코드가 동기식 또는 비동기식으로 실행됩니까?

Task.RunUI 스레드를 차단하지 않도록 메소드를 대기 가능하고 비동기 적으로 만들 려면 동기화 코드를 래핑해야 합니까?

내 메서드가 Task반환인지 또는 반환 인지 알아 내려고 노력 중 입니다. 비 동기화하기 위해 Task<T>코드를 래핑해야합니까?Task.Run

어리석은 질문 확실하지만 사람들이 내부에 비동기가없고 Task.Run또는에 싸여 있지 않은 코드를 기다리는 곳의 예를 봅니다 StartNew.


30
첫 번째 스 니펫이 경고를 표시하지 않습니까?
svick 2016 년

답변:


586

먼저, "비동기"( async) 라는 용어를 정리해 보자 . 에서 async방법, 그 "수율"포인트는 await표현은.

이것은 "백그라운드 스레드에서 실행"을 의미하기 위해 MSDN 문서에서 수년 동안 사용 된 "비동기"라는 용어와는 매우 다릅니다.

추가 async로이 문제를 혼동시키기 위해 "대기 가능" 과 는 매우 다릅니다. async반환 유형이 대기 할 수없는 일부 메소드와 대기하지 않는 유형을 리턴하는 많은 메소드가 있습니다 async.

그들이 아닌 것에 대해 충분히 ; 여기에 그들이 무엇 있습니다 :

  • async키워드는 (즉, 그것은 수있는 비동기 방식을 허용 await표현). async방법은 반환 할 수 있습니다 Task, Task<T>(당신이해야하는 경우), 또는 void.
  • 특정 패턴을 따르는 모든 유형을 기다릴 수 있습니다. 가장 일반적인 대기 유형은 TaskTask<T>입니다.

따라서 귀하의 질문을 " 대기 가능한 방식으로 백그라운드 스레드 에서 작업 어떻게 실행할 수 있습니까?"로 재구성하면 대답은 다음과 Task.Run같습니다.

private Task<int> DoWorkAsync() // No async because the method does not need await
{
  return Task.Run(() =>
  {
    return 1 + 2;
  });
}

그러나이 패턴은 좋지 않은 접근 방식입니다 (아래 참조).

그러나 귀하의 질문이 " async차단하는 대신 호출자에게 되돌릴 수 있는 메소드를 작성하는 방법"인 경우, 메소드를 선언 하고 "수확"지점에 async사용 하는 것이 답입니다 await.

private async Task<int> GetWebPageHtmlSizeAsync()
{
  var client = new HttpClient();
  var html = await client.GetAsync("http://www.example.com/");
  return html.Length;
}

따라서 기본적인 것의 패턴은 async코드가 await표현의 "대기 가능"에 의존 하도록하는 것입니다 . 이러한 "대기 가능 항목"은 다른 async방법이거나 대기 가능 항목을 반환하는 일반적인 방법 일 수 있습니다 . 반환 정기 방법은 Task/ Task<T> 사용하는 Task.Run백그라운드 스레드에서 코드를 실행하거나 (더 일반적으로) 그들이 사용할 수 있습니다 TaskCompletionSource<T>또는 바로 가기 (하나 TaskFactory.FromAsync, Task.FromResult등). 나는 하지 않는 전체 방법을 포장하는 것이 좋습니다 Task.Run; 동기 메소드는 동기 서명을 가져야하며, 다음과 같이 랩핑되어야하는지 소비자에게 맡겨야합니다 Task.Run.

private int DoWork()
{
  return 1 + 2;
}

private void MoreSynchronousProcessing()
{
  // Execute it directly (synchronously), since we are also a synchronous method.
  var result = DoWork();
  ...
}

private async Task DoVariousThingsFromTheUIThreadAsync()
{
  // I have a bunch of async work to do, and I am executed on the UI thread.
  var result = await Task.Run(() => DoWork());
  ...
}

나는이 async/ await소개 내 블로그를; 마지막에는 좋은 후속 리소스가 있습니다. MSDN 문서 async도 비정상적으로 좋습니다.


8
@sgnsajgon : 그렇습니다. async방법은 반환해야합니다 Task, Task<T>또는 void. TaskTask<T>awaitable이다; void아니다.
Stephen Cleary

3
실제로, async void메소드 서명은 컴파일 될 것이다. 비동기 작업에 대한 포인터를
잃어 버리는

4
@TopinFrassi : 그렇습니다. 컴파일되지만 void기다릴 수는 없습니다.
Stephen Cleary

4
@ ohadinho : 아니요, 블로그 게시물에서 말하는 것은 전체 메소드가 호출 될 때입니다 Task.Run( DoWorkAsync이 답변 과 같이 ). UI 컨텍스트에서 메소드 Task.Run호출 하는 데 사용 하는 것이 적절합니다 (예 DoVariousThingsFromTheUIThreadAsync:).
Stephen Cleary

2
예, 정확히 메소드 Task.Run호출 하는 데 사용하는 것이 유효 하지만 Task.Run메소드 코드의 모든 (또는 거의 모든) 코드가있는 경우 반 패턴입니다. 해당 메소드를 동기식으로 유지 Task.Run하고 레벨을 올리십시오.
Stephen Cleary 2016 년

22

와 방법을 장식 할 때 기억해야 할 가장 중요한 일 중 하나는 비동기은 적어도 있다는 것입니다 하나의 await를 메소드 내부 연산자. 귀하의 예에서는 TaskCompletionSource를 사용하여 아래에 표시된 것처럼 번역합니다 .

private Task<int> DoWorkAsync()
{
    //create a task completion source
    //the type of the result value must be the same
    //as the type in the returning Task
    TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
    Task.Run(() =>
    {
        int result = 1 + 2;
        //set the result to TaskCompletionSource
        tcs.SetResult(result);
    });
    //return the Task
    return tcs.Task;
}

private async void DoWork()
{
    int result = await DoWorkAsync();
}

26
Task.Run () 메서드에서 반환 한 작업을 반환하고 본문을 변경하여 결과를 반환하는 대신 TaskCompletionSource를 사용하는 이유는 무엇입니까?
아이러니

4
참고 사항입니다. "비동기 무효"서명이있는 메소드는 일반적으로 나쁜 습관이며 UI 교착 상태를 매우 쉽게 유발할 수 있으므로 잘못된 코드로 간주됩니다. 주요 예외는 비동기 이벤트 핸들러입니다.
Jazzeroki

12

Task.Run을 사용하여 메서드를 실행하면 Task가 스레드 풀에서 스레드를 가져와 해당 메서드를 실행합니다. UI 스레드의 관점에서 볼 때 UI 스레드를 차단하지 않으므로 "비동기"입니다. 일반적으로 사용자 상호 작용을 처리하기 위해 많은 스레드가 필요하지 않으므로 데스크톱 응용 프로그램에 적합합니다.

그러나 웹 응용 프로그램의 경우 각 요청은 스레드 풀 스레드에 의해 서비스되므로 이러한 스레드를 저장하여 활성 요청 수를 늘릴 수 있습니다. 비동기 작업을 시뮬레이션하기 위해 스레드 풀 스레드를 자주 사용하는 것은 웹 응용 프로그램에서 확장 할 수 없습니다.

True Async는 파일 / DB 액세스 등과 같은 I / O 작업에 스레드를 반드시 사용해야하는 것은 아닙니다. I / O 작업에 스레드가 필요하지 않은 이유를 이해하려면이 내용을 읽을 수 있습니다. http://blog.stephencleary.com/2013/11/there-is-no-thread.html

간단한 예제에서는 순수한 CPU 바운드 계산이므로 Task.Run을 사용하는 것이 좋습니다.


따라서 웹 API 컨트롤러 내에서 동기 외부 API를 사용해야하는 경우 Task.Run ()에서 동기 호출을 래핑해서는 안됩니까? 말했듯이 초기 요청 스레드는 차단되지 않지만 다른 풀 스레드를 사용하여 외부 API를 호출합니다. 실제로 나는 이것이 좋은 방법이라고 생각합니다. 왜냐하면 이론적으로 두 개의 풀 스레드를 사용하여 많은 요청을 처리 할 수 ​​있습니다. 예를 들어 하나의 스레드는 많은 들어오는 요청을 처리하고 다른 하나는 모든 요청에 ​​대해 외부 API를 호출 할 수 있습니까?
stt106

동의합니다. Task.Run () 내에서 모든 동기 호출을 완전히 래핑해서는 안됩니다. 나는 잠재적 인 문제를 지적하고 있습니다.
zheng yu

1
@ stt106 I should NOT wrap the synchronous call in Task.Run()맞습니다. 그렇다면 스레드를 전환하는 것입니다. 즉, 초기 요청 스레드를 차단 해제하지만 다른 요청을 처리하는 데 사용할 수있는 스레드 풀에서 다른 스레드를 가져옵니다. 유일한 결과는 통화가 절대적으로 제로 게인으로 완료 될 때 컨텍스트 전환 오버 헤드입니다
Saeb Amini
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.