언제 Task.Yield ()를 사용합니까?


218

나는 async / await을 Task많이 사용 Task.Yield()하고 있지만 왜이 방법이 필요한지 이해할 수없는 모든 설명에도 불구하고 사용 하고 정직합니다.

누군가 Yield()가 필요한 곳에서 좋은 모범을 보일 수 있습니까 ?

답변:


241

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

차단하지 않고 코드를 비동기식으로 실행해야하는 중요한 API를 만드는 경우 호출 된 메서드가 동 기적으로 (효과적으로 차단) 실행될 가능성이 있으면를 사용 await Task.Yield()하면 메서드가 비동기식으로 돌아가고 그 시점에서 제어합니다. 나머지 코드는 현재 컨텍스트에서 나중에 실행됩니다 (이 시점에서 여전히 동 기적으로 실행될 수 있음).

"장기 실행"초기화가 필요한 비동기 메소드를 만드는 경우에도 유용합니다.

 private async void button_Click(object sender, EventArgs e)
 {
      await Task.Yield(); // Make us async right away

      var data = ExecuteFooOnUIThread(); // This will run on the UI thread at some point later

      await UseDataAsync(data);
 }

Task.Yield()호출 하지 않으면 메소드는에 대한 첫 번째 호출까지 동기식으로 실행됩니다 await.


26
나는 여기서 뭔가를 잘못 해석하고 있다고 생각합니다. 만약 await Task.Yield()힘 비동기 될 수있는 방법, 왜 우리가 "진짜"비동기 코드를 작성 귀찮게 것? 강력한 동기화 방법을 상상해보십시오. 그것은 비동기, 단지 추가하려면 asyncawait Task.Yield()시작과 마술, 그것은 비동기 될 것인가? 그것은 모든 동기화 코드를 래핑 Task.Run()하고 가짜 비동기 메서드를 만드는 것과 거의 같습니다 .
Krumelur

14
@Krumelur 큰 차이가 있습니다-내 예를보십시오. 를 사용 Task.Run하여 구현 ExecuteFooOnUIThread하면 UI 스레드가 아닌 스레드 풀에서 실행됩니다. 을 사용하면 await Task.Yield()후속 코드가 현재 컨텍스트에서 (나중에) 계속 실행되는 방식으로 비동기식으로 강제합니다. 일반적으로하는 일은 아니지만 이상한 이유로 필요한 경우 옵션이있는 것이 좋습니다.
리드 콥시

7
한 가지 더 질문 : ExecuteFooOnUIThread()매우 오래 실행 된 경우 여전히 특정 시점에서 오랫동안 UI 스레드를 차단하고 UI를 응답하지 않게 만드는 것이 맞습니까?
Krumelur

7
@Krumelur 그렇습니다. 즉시는 아닙니다-나중에 일어날 것입니다.
리드 콥시

33
이 답변은 기술적으로는 정확하지만 "나머지 코드는 나중에 실행할 것"이라는 말이 너무 추상적이며 오해의 소지가 있습니다. Task.Yield () 이후 코드의 실행 일정은 구체적인 SynchronisationContext에 크게 의존합니다. MSDN 설명서에 따르면 "대부분의 UI 환경에서 UI 스레드에 존재하는 동기화 컨텍스트는 입력 및 렌더링 작업보다 컨텍스트에 게시 된 작업의 우선 순위를 지정하는 경우가 많습니다. 따라서 대기 작업에 의존하지 마십시오. Task.Yield () UI를 반응 적으로 유지합니다. "
Vitaliy Tsvayer

36

내부적 await Task.Yield()으로는 현재 동기화 컨텍스트 또는 임의의 풀 스레드 (있는 경우 SynchronizationContext.Current) 에서 연속을 큐에 넣습니다 null.

그것은되어 효율적으로 구현 사용자 정의 awaiter한다. 동일한 효과를 생성하는 덜 효율적인 코드는 다음과 같이 간단 할 수 있습니다.

var tcs = new TaskCompletionSource<bool>();
var sc = SynchronizationContext.Current;
if (sc != null)
    sc.Post(_ => tcs.SetResult(true), null);
else
    ThreadPool.QueueUserWorkItem(_ => tcs.SetResult(true));
await tcs.Task;

Task.Yield()이상한 실행 흐름 변경에 대한 바로 가기로 사용할 수 있습니다. 예를 들면 다음과 같습니다.

async Task DoDialogAsync()
{
    var dialog = new Form();

    Func<Task> showAsync = async () => 
    {
        await Task.Yield();
        dialog.ShowDialog();
    }

    var dialogTask = showAsync();
    await Task.Yield();

    // now we're on the dialog's nested message loop started by dialog.ShowDialog 
    MessageBox.Show("The dialog is visible, click OK to close");
    dialog.Close();

    await dialogTask;
    // we're back to the main message loop  
}

즉, 적절한 작업 스케줄러 Task.Yield()로 교체 할 수없는 경우는 생각할 수 없습니다 Task.Factory.StartNew.

또한보십시오:


귀하의 예에서 거기에 무엇이 var dialogTask = await showAsync();있습니까?
Erik Philips

@ErikPhilips var dialogTask = await showAsync()await showAsync()표현식이를 반환하지 않기 때문에 컴파일 하지 않습니다 Task(없는 경우와 달리 await). 즉, 그렇게하면 await showAsync()대화 상자를 닫은 후에 만 ​​실행이 재개됩니다. 그렇습니다. window.ShowDialog동기 API 이기 때문에 여전히 메시지를 펌핑 하기 때문 입니다. 이 코드에서 대화 상자가 계속 표시되는 동안 계속하고 싶습니다.
noseratio

5

Task.Yield()비동기 재귀를 수행 할 때 스택 오버플로를 방지하는 것이 한 가지 용도입니다 . Task.Yield()동기 연속을 방지합니다. 그러나 이로 인해 Triynko에서 지적한대로 OutOfMemory 예외가 발생할 수 있습니다. 끝없는 재귀는 여전히 안전하지 않으며 재귀를 루프로 다시 작성하는 것이 좋습니다.

private static void Main()
    {
        RecursiveMethod().Wait();
    }

    private static async Task RecursiveMethod()
    {
        await Task.Delay(1);
        //await Task.Yield(); // Uncomment this line to prevent stackoverlfow.
        await RecursiveMethod();
    }

4
이렇게하면 스택 오버플로가 방지 될 수 있지만 오래 실행하면 시스템 메모리가 부족하게됩니다. 각 반복은 외부 작업이 내부 작업을 기다리고 있기 때문에 완료되지 않은 새 작업을 생성합니다.이 작업은 또 다른 내부 작업을 기다리고 있습니다. 이것은 괜찮습니다. 또는 완료되지 않은 가장 바깥 쪽의 작업 하나를 단순히 재귀 대신 반복하도록 할 수 있습니다. 작업은 완료되지 않았지만 그 중 하나만있을 것입니다. 루프 내부에서 원하는 것을 생성하거나 기다릴 수 있습니다.
Triynko

스택 오버플로를 재현 할 수 없습니다. 그것을 await Task.Delay(1)막기에 충분한 것 같습니다 . (콘솔 앱, .NET Core 3.1, C # 8)
Theodor Zoulias

-8

Task.Yield() 비동기 메소드의 모의 구현에 사용될 수 있습니다.


4
세부 사항을 제공해야합니다.
PJProudhon

3
이를 위해 Task.CompletedTask를 사용하고 싶습니다 . 자세한 내용 은 이 msdn 블로그 게시물의 Task.CompletedTask 섹션을 참조하십시오 .
Grzegorz Smulko

2
Task.CompletedTask 또는 Task.FromResult 사용의 문제점은 메소드가 비동기 적으로 실행될 때만 나타나는 버그를 놓칠 수 있다는 것입니다.
Joakim MH
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.