36

나에 대해 읽었 어.Task.Yield, 그리고 자바 스크립트 개발자로서 저는 그것이 그것이 직업이라고 말할 수 있습니다.정확하게같은setTimeout(function (){...},0);메인 쓰레드가 다른 것들을 처리하도록하는 관점에서 :

"모든 힘을 다 쓰지 말고 시간에서 해방하십시오 - 그래서 다른 사람들은   너무 ... "

js에서는 긴 루프에서 특히 작동합니다. (브라우저를 정지시키지 마라.)

그러나 나는이 예를 보았다.이리:

public static async Task < int > FindSeriesSum(int i1)
{
    int sum = 0;
    for (int i = 0; i < i1; i++)
    {
        sum += i;
        if (i % 1000 == 0) ( after a bulk , release power to main thread)
            await Task.Yield();
    }

    return sum;
}

JS 프로그래머로서 나는 그들이 무엇을했는지 이해할 수있다.

그러나 C #프로그래머로서 나는 스스로에게 묻습니다. 왜 그것을위한 작업을하지 않습니까?

 public static async Task < int > FindSeriesSum(int i1)
    {
         //do something....
         return await MyLongCalculationTask();
         //do something
    }

의문

Js에서는 Task를 열 수 없습니다 (네, 저는 실제로 웹 작업자와 함께 할 수 있습니다.). 하지만 C #제가 할수 있어요.

그렇다면- 내가 풀어줄 수있는 동안 때때로 풀어주는 것을 왜 귀찮게 하는가?

편집하다

참조 추가 :

에서이리:enter image description here

에서이리(다른 eBook) :

enter image description here


  • 그것은 나쁜 예입니다. 왜냐하면Task.YieldUI를 응답으로 유지하십시오. 또한,asyncawaitUI 스레드를 비우지 마십시오. 계산 시간이 오래 걸리면 다음을 사용해야합니다.Task.Run. - Stephen Cleary
  • @StephenCleary 스레드 풀에서 작업이 이미 실행되고 스레드 풀이 중요한 리소스 인 다중 스레드 응용 프로그램 (ASP.NET 또는 유사)을 고려하십시오. CPU를 많이 사용하는 장기 실행 작업이await Task.Yield()매 초마다 다른 짧은 실행 작업은 오랫동안 대기열에 대기하지 않고 기회를 얻습니다. 이것이 공정한 행동이라고 생각하지 않으십니까? - springy76
  • @ springy76 : .NET의 자체 조정 스레드 풀과 결합 된 모든 최신 OS에 내장 된 선점 형 스레드 전환은 수동보다 훨씬 잘 작동합니다await Task.Yield(). - Stephen Cleary

6 답변


49

볼 때 :

await Task.Yield();

당신은 이런 식으로 생각할 수 있습니다 :

await Task.Factory.StartNew( 
    () => {}, 
    CancellationToken.None, 
    TaskCreationOptions.None, 
    SynchronizationContext.Current != null?
        TaskScheduler.FromCurrentSynchronizationContext(): 
        TaskScheduler.Current);

이 모든 작업은 향후 비동기 적으로 연속성이 유지되도록합니다. 으로비동기 적으로실행 제어가 호출자에게 돌아갈 것입니다.async메소드와 연속 콜백은아니동일한 스택 프레임에서 발생합니다.

정확히 어떤 스레드에서 발생하는지는 호출자 스레드의 동기화 컨텍스트에 따라 다릅니다.

UI 스레드의 경우, 메시지 루프의 미래의 반복에 따라 계속 될 것입니다.Application.Run(WinForms) 또는Dispatcher.Run(WPF). 내부적으로는 Win32PostMessageAPI는 사용자 정의 메시지를 UI 스레드의 메시지 대기열에 게시합니다. 그만큼await이 메시지가 펌핑되고 처리되면 계속 콜백이 호출됩니다. 정확히 언제 이런 일이 발생할지 완전히 통제 할 수 없습니다.

게다가 Windows에는 메시지를 펌핑하기위한 우선 순위가 있습니다.정보 : 창 메시지 우선 순위. 가장 관련성이 높은 부분 :

이 계획에서 우선 순위는 3 단계로 간주 될 수 있습니다. 모든   게시 된 메시지는 사용자 입력 메시지보다 우선 순위가 높기 때문에   그들은 다른 대기열에 있습니다. 그리고 모든 사용자 입력 메시지는   WM_PAINT 및 WM_TIMER 메시지보다 높은 우선 순위.

그래서, 만약 당신이await Task.Yield()UI 응답 성을 유지하려고 시도 할 때 메시지 루프를 반환하기 위해 UI 스레드의 메시지 루프를 실제로 막을 위험이 있습니다. 일부 보류중인 사용자 입력 메시지는 물론WM_PAINTWM_TIMER게시 된 계속 메시지보다 우선 순위가 낮습니다. 따라서, 만약 당신이await Task.Yield()단단한 루프에서 여전히 UI를 차단할 수 있습니다.

이것은 자바 스크립트와 다른 점입니다.setTimer당신이 질문에서 언급 한 비유. 에이setTimer콜백이 호출됩니다.모든 사용자 입력 메시지가 브라우저의 메시지 펌프에 의해 처리되었습니다.

그래서,await Task.Yield()UI 스레드에서 백그라운드 작업을 수행하는 데 적합하지 않습니다. 실제로 UI 스레드에서 백그라운드 프로세스를 실행할 필요는 거의 없지만 가끔씩 수행합니다. 편집기 구문 강조, 맞춤법 검사 등.이 경우 프레임 워크의 유휴 인프라를 사용하십시오.

예 : WPF로 할 수있는 작업await Dispatcher.Yield(DispatcherPriority.ApplicationIdle):

async Task DoUIThreadWorkAsync(CancellationToken token)
{
    var i = 0;

    while (true)
    {
        token.ThrowIfCancellationRequested();

        await Dispatcher.Yield(DispatcherPriority.ApplicationIdle);

        // do the UI-related work item
        this.TextBlock.Text = "iteration " + i++;
    }
}

WinForms의 경우,Application.Idle행사:

// await IdleYield();

public static Task IdleYield()
{
    var idleTcs = new TaskCompletionSource<bool>();
    // subscribe to Application.Idle
    EventHandler handler = null;
    handler = (s, e) =>
    {
        Application.Idle -= handler;
        idleTcs.SetResult(true);
    };
    Application.Idle += handler;
    return idleTcs.Task;
}

그것을 추천합니다UI 스레드에서 실행되는 백그라운드 작업이 반복 될 때마다 50ms를 초과하지 않아야합니다.

UI가 아닌 스레드의 경우동기화 문맥이없는 경우,await Task.Yield()연속을 임의의 풀 스레드로 전환합니다. 그것이 될 것이라는 보장은 없다.다른스레드를 현재 스레드에서 가져온 경우에만비동기계속. 만약ThreadPool그것은 굶주리고있다, 그것은 동일한 실에 계속을 계획 할지도 모른다.

ASP.NET에서,하고있다.await Task.Yield()doesn''t는 전혀 언급하지 않는다.@ StephenCleary의 대답. 그렇지 않으면 여분의 스레드 스위치로 웹 응용 프로그램 성능을 해칠뿐입니다.

그래서,await Task.Yield()유능한?IMO,별로. 그것은을 통해 연속을 실행하는 지름길로 사용될 수 있습니다.SynchronizationContext.Post또는ThreadPool.QueueUserWorkItem, 만약 당신이 정말로 당신의 메소드의 일부에 비동기를 부과해야한다면.

인용 한 책에 관해서, 제 의견으로는Task.Yield틀렸다. 나는 위의 UI 스레드가 왜 틀린 지 설명했다. UI가 아닌 풀 스레드의 경우"실행할 스레드의 다른 작업", 같은 사용자 지정 작업 펌프를 실행하지 않는 한스티븐 투브AsyncPump.

의견에 답변하도록 업데이트 됨 :

... 어떻게 asynchronouse 작업 및 동일한 스레드에 머물 수 있습니다.   ? ..

간단한 예를 들면 다음과 같습니다. WinForms app :

async void Form_Load(object s, object e) 
{ 
    await Task.Yield(); 
    MessageBox.Show("Async message!");
}

Form_Load호출자에게 반환됩니다 (해고 된 WinFroms 프레임 워크 코드).Load이벤트)를 실행 한 다음 메시지 상자가 비동기 적으로 표시됩니다.Application.Run(). 연속 콜백은 다음과 함께 대기합니다.WinFormsSynchronizationContext.Post내부적으로 UI 스레드의 메시지 루프에 비공개 Windows 메시지를 게시합니다. 콜백은이 메시지가 펌핑 될 때 실행되며 여전히 동일한 스레드에서 수행됩니다.

콘솔 앱에서는 다음과 같은 유사한 직렬화 루프를 실행할 수 있습니다.AsyncPump위에 언급했듯이.


  • Notting, hi :-). 너는 말했다 :" 현재 스레드와 다른 스레드가 될 것이라는 보장은 없으며 비동기 연속성 만 보장됩니다 ". -하지만 같은 스레드에 머물러 있다면 연산이 동기 연산이됩니다. 아니 ? 다른 말로하면, 비동기식 작업이면서 동일한 스레드에 머무를 수있는 방법은 무엇입니까? asynchrounsey는 계속에 관한 것이며 - "나는 돌아와서 멈추는 곳에서 계속 될 것"을 의미합니다. 하지만 같은 스레드에 있다면, 그것은 oprtaion ..... 결코 떠나지 않을 가능성이 더 높습니다? - Royi Namir
  • Hey @RoyiNamir, 동일한 스레드에서 비동기 적으로 계속 진행할 수 있습니다. 내 업데이트를 확인하십시오. - noseratio
  • 네 :)하지만 당신은에 코멘트를 썼습니다.비 UI 스레드섹션, 어느 날 혼란 ..... - Royi Namir
  • @RoyiNamir, UI가 아닌 스레드에서도 그렇게 할 수 있습니다.SynchronizationContext. 봤어?AsyncPump? 결국 비동기는 멀티 스레딩을 가정하지 않습니다. 장래에 결과를 제공 할 것입니다. 참고로,ThreadPools.context가 없으면 동일한 스레드에서 계속이 발생할 가능성이 있습니다 (예 : 동일한 풀 스레드가 비동기 I / O 작업의 완료를 나중에 시작할 수 있음). - noseratio
  • @bN_, 있습니다 :protected virtual Task OverridableMethod() { return Task.Task.CompletedTask; }. 노트async가상 메서드 서명의 일부가 아니므로 파생 클래스에서이 메서드를 재정의 할 때 여전히 사용할 수 있습니다. - noseratio

16

나는 단지 발견했다.Task.Yield두 가지 시나리오에서 유용합니다.

  1. 테스트중인 코드가 비동기 상태에서 적절하게 작동하는지 확인하기위한 유닛 테스트.
  2. 신원 확인 코드가있는 명확하지 않은 ASP.NET 문제를 해결하려면~ 할 수 없다.동 기적으로 완료하다.


6

아니요. 정확히 사용법은 아닙니다.setTimeout컨트롤을 UI로 반환합니다. Javascript에서는 항상 UI 업데이트를setTimeout보류중인 UI 작업은 타이머보다 우선 순위가 높습니다. 그러나 최소 지연 시간은 몇 밀리 초입니다.await Task.Yield();하지 않습니다.

yield가 주 스레드에서 수행되도록 보장 할 수는 없지만 반대로 yield를 호출하는 코드는 UI 작업보다 우선 순위가 높습니다.

"대부분의 UI에서 UI 스레드에있는 동기화 컨텍스트   환경은 종종 컨텍스트에 게시 된 작업의 우선 순위를 높입니다.   입력 및 렌더링 작업보다. 이런 이유로 기다릴 필요가 없습니다.   Task.Yield (); UI를 반응 적으로 유지합니다. "

참고 :MSDN : Task.Yield 메서드


  • 그것에 의지하지 않는다면 - 그것이 무엇을 위해서입니까? REAL 예제를 제공 할 수 있습니까? - Royi Namir
  • @RoyiNamir : 멀티 스레딩을 처리 할 수 있지만 UI 응답 성을 유지하는 방식은 신뢰할 수 없습니다. 메서드를 비동기로 만들지 만 CPU의 주 스레드와 경쟁하지 않도록합니다. - Guffa
  • 주 스레드 우선 순위를 얻을 수 있다면, 왜 그것을 사용하고 싶습니까? 당신이 말하는 것은 코드가 비동기가 아닌 동기화를 실행할 수 있다는 것입니다. - Royi Namir
  • @RoyiNamir : 코드는 비동기 적으로 실행되지만 작업 우선 순위에 따라 동기 적으로 실행된다는 인상을받을 수 있습니다. 비동기 작업은 무언가가 일어날 때까지 기다리는 작업에 더 적합합니다. CPU 집약적 인 작업의 경우 오히려AsParallel방법. - Guffa

2

무엇보다도 먼저 내가 분명히하자.Yield정확히 같은 것은 아니다.setTimeout(function (){...},0);. JS는 단일 스레드 환경에서 실행되므로 다른 작업을 수행 할 수있는 유일한 방법입니다. 거의협동 멀티 태스킹. .net은 명시 적 멀티 스레딩 기능을 갖춘 선점 형 멀티 태스킹 환경에서 실행됩니다.

지금으로 돌아 가기Thread.Yield. 내가 말했듯이 .net은 선점 시대에 살지만, 그보다 조금 더 복잡합니다. 기음#await/async상태 머신에 의해 통치되는 멀티 태스킹 모드의 흥미로운 혼합을 만듭니다. 그래서 생략하면Yield귀하의 코드에서 그것은 단지 스레드를 차단하고 그게 전부입니다. 만약 당신이 그것을 정기적 인 작업으로 만들고 start (또는 thread)를 호출하면 task.Result가 호출 될 때 스레드를 병렬로 호출하고 나중에 호출하는 것을 막을 것입니다. 네가 할 때 어떻게 될까?await Task.Yield();더 복잡합니다. 논리적으로 JS와 비슷한 호출 코드를 차단 해제하고 실행을 계속합니다. 실제로 무엇을하는지 - 다른 스레드를 선택하여 스레드를 호출하는 선점 환경에서 실행을 계속합니다. 그래서 그것은 처음까지 스레드를 호출합니다.Task.Yield그리고 나서 그것은 자신의 것입니다. 후속 통화 :Task.Yield명백하게 아무것도하지 마라.

간단한 데모 :

class MainClass
{
    //Just to reduce amont of log itmes
    static HashSet<Tuple<string, int>> cache = new HashSet<Tuple<string, int>>();
    public static void LogThread(string msg, bool clear=false) {
        if (clear)
            cache.Clear ();
        var val = Tuple.Create(msg, Thread.CurrentThread.ManagedThreadId);
        if (cache.Add (val))
            Console.WriteLine ("{0}\t:{1}", val.Item1, val.Item2);
    }

    public static async Task<int> FindSeriesSum(int i1)
    {
        LogThread ("Task enter");
        int sum = 0;
        for (int i = 0; i < i1; i++)
        {
            sum += i;
            if (i % 1000 == 0) {
                LogThread ("Before yield");
                await Task.Yield ();
                LogThread ("After yield");
            }
        }
        LogThread ("Task done");
        return sum;
    }

    public static void Main (string[] args)
    {
        LogThread ("Before task");
        var task = FindSeriesSum(1000000);
        LogThread ("While task", true);
        Console.WriteLine ("Sum = {0}", task.Result);
        LogThread ("After task");
    }
}

결과는 다음과 같습니다.

Before task     :1
Task enter      :1
Before yield    :1
After yield     :5
Before yield    :5
While task      :1
Before yield    :5
After yield     :5
Task done       :5
Sum = 1783293664
After task      :1
  • Mac OS X에서 모노 4.5로 출력되는 결과는 다른 설정에 따라 다를 수 있습니다.

이사하면Task.Yield메서드의 맨 위에 비동기로 시작하여 호출 스레드를 차단하지 않습니다.

결론:Task.Yield동기화 및 비동기 코드를 혼합 할 수 있습니다. 다소 더 현실적인 시나리오 : 계산 작업이 많고 로컬 캐시 및 태스크가 필요합니다.CalcThing. 이 메서드에서는 항목이 캐시에 있는지 확인합니다 (예인 경우). 항목이없는 경우 항목을 반환합니다.Yield그것을 계산하기 위해 진행한다. 실제로 책의 샘플은 무의미합니다. 아무런 도움이되지 않기 때문입니다. GUI 인터랙티비티에 대한 그들의 언급은 잘못되었다. (UI 쓰레드는Yield, 당신은 결코 그것을해서는 안됩니다.MSDN"Task.Yield ()를 기다리는 것에 의존하지 말고 UI를 반응 적으로 유지하십시오."


  • JS에 대한 비유는 C #의 GUI 응용 프로그램이 UI를 실행하는 단일 기본 스레드 (JS 단일 루프와 동일)가있는 환경에서였습니다. (그리고 JS에서 - 해결책은setTimeout(...,0 ) - Royi Namir
  • 데모 사랑 :) - Noctis

0

장기 실행 함수가 백그라운드 스레드에서 실행될 수 있다고 가정합니다. 예를 들어 UI 상호 작용이 있기 때문에 그렇지 않은 경우 UI가 실행되는 동안 UI 차단을 방지 할 방법이 없기 때문에 실행 시간이 사용자에게 문제가되지 않도록 충분히 짧게 유지해야합니다.

또 다른 가능성은 백그라운드 스레드보다 장기 실행 기능이 더 많다는 것입니다. 이 시나리오에서는 몇 가지 함수가 스레드를 모두 차지하지 못하도록하는 것이 더 좋을 수도 있습니다 (또는 중요하지 않을 수도 있습니다.).


  • 첫 번째 사례는 유효하지만 두 번째 사례는 실제로 유효하지 않습니다. 스레드가있는 것보다 더 많은 일이나 노동자가 있다면 그 시간을 공유하기 만하면됩니다. UI 스레드에 대한 일부 작업에 도움이되지 않습니다. 그렇게하지 않으면 UI 스레드가 유휴 상태가되어 작업자 스레드에 시간을 낭비하게됩니다. - Servy
  • @hvd 말하기UI 상호 작용- 나타날 수있는 코드에 대해 이야기하고 있습니다 (Before || After)await. 권리 ? - Royi Namir
  • @hvd 당신은 가지고 있어야합니다.많은스레드가있는 것보다 더 많은 작업이 발생합니다. 쓰레드 기아라고합니다. 가장 극단적 인 경우를 제외하고는 스레드 스케줄러가 각 스레드의 실행을 변경하여 각 작업을 완료까지 실행하지 않고 각각의 짧은 시간 조각을 실행할 수 있도록함으로써 문제를 해결할 수 있습니다. 위에선택 사항 인 작업자를 정확히 가질 수있는 스레드 풀을 사용하여 문제를 훨씬 효과적으로 해결할 수 있으며 처리량을 최적화하는 방법으로 수행 할 실제 작업을 큐업 할 수 있습니다. - Servy
  • @hvd 아니요, 스레드의 스케줄러가 그다지 열악하지 않기 때문에 문제가되는 것은 아닙니다. 그리고 UI 스레드가 지연을 감지하기 위해 굶주 릴 필요가있는 시간이 꽤 높은. 다른 스레드는 CPU 시간을 영원히 유지할 수있는 옵션이 없습니다. 사실 그들은 그 문제에 대해 거의 통제권을 갖고 있지 않습니다. UI 스레드 관리와 달리 협업 시스템이 아닙니다.UI 스레드 내에서누군가가 의도적으로 UI 스레드를 원하는만큼 오랫동안 사용할 수 있기 때문에 다른 모든 사람들이 굶어 죽을 수 있습니다. - Servy
  • @hvd 기술적으로 내가 언급 한 두 번째 옵션을 수행하는 한 가지 방법은이를 수행하는 데 상당히 저조 된 설계 방법 일뿐입니다. 작업이 분해되는 데 적합하다면 분해하십시오. 그렇지 않으면 막대기 만 사용하세요.LongRunning그것보다는 오히려yield여기 저기. - Servy

0

나는 Task.Yield를 사용할 때 진짜 응답을 아무도 제공하지 않았다고 생각한다. 태스크가 결코 끝나지 않는 루프 (또는 긴 동기화 작업)를 사용하고 잠재적으로 스레드 풀 스레드를 독점적으로 보유 할 수 있으면 (다른 스레드가이 스레드를 사용할 수 없게되는 경우) 대부분 필요합니다. 루프 내부에서 코드가 동 기적으로 실행되는 경우 이런 일이 발생할 수 있습니다. 그만큼Task.Yield reschedules스레드 풀 대기열에 대한 작업과 스레드를 대기 한 다른 작업을 실행할 수 있습니다.

예제 :

  CancellationTokenSource cts;
  void Start()
  {
        cts = new CancellationTokenSource();

        // run async operation
        var task = Task.Run(() => SomeWork(cts.Token), cts.Token);
        // wait for completion
        // after the completion handle the result/ cancellation/ errors
    }

    async Task<int> SomeWork(CancellationToken cancellationToken)
    {
        int result = 0;

        bool loopAgain = true;
        while (loopAgain)
        {
            // do something ... means a substantial work or a micro batch here - not processing a single byte

            loopAgain = /* check for loop end && */  cancellationToken.IsCancellationRequested;
            if (loopAgain) {
                // reschedule  the task to the threadpool and free this thread for other waiting tasks
                await Task.Yield();
            }
        }
        cancellationToken.ThrowIfCancellationRequested();
        return result;
    }

    void Cancel()
    {
        // request cancelation
        cts.Cancel();
    }


  • 두 번째 생각에서 그런 식으로 사용할 수도 있지만 좋은 디자인 같지는 않습니다 : CPU 기반 작업의 큰 더미를 스레드 풀 (처리 할 수있는 것보다 많음)에 대기 시켜서 ~와 함께 얻을 수있는Task.Yield()동료들에게 실행할 수있는 기회를 제공합니다. 이와 같은 동시 CPU 바인딩 시나리오를위한 더 나은 옵션이 있습니다.Parallel또는TPL 데이터 흐름. - noseratio
  • @noseratio 루프의 코드가 사용자 정의 처리기를 실행하는 경우이 패턴을 사용할 수 있습니다. 코드는 비동기 또는 동기식 인 경우 동작 방식을 알 수 없습니다. 따라서 예방책으로 루프 끝에서 Task.Yield를 갖는 것이 좋습니다. 이것은 메시지 처리 버스에서 일반적입니다. 버스가 핸들러를 인스턴스화하고 메시지를 처리 할 때. 병렬로 실행되는 작업자가 많은 경우 스레드 풀 스레드를 제거 할 수 있습니다. 나는 그것을 사용하여 노동자 코디네이터를 구현했다.github.com/BBGONE/REBUS- TaskCoordinator - Maxim T
  • 그리고 한 가지 더 - 이것은 테스트 될 수 있습니다. TaskCoordinator 테스트 랩이 있습니다.github.com/BBGONE/TaskCoordinator이 패턴을 사용합니다. 메시지를 처리하기 위해 많은 작업을 병렬로 실행하도록 구성한 경우 Task.Yield를 사용하지 않으면 콘솔의 메시지가 표시되지 않습니다 - 모든 풀 스레드가 사용 중이며 스레드 풀이 더 이상 추가하지 않기 때문에 - 높은 CPU 사용량. 그러나 Task.Yield를 사용하면 상태 메시지가 콘솔에 나타납니다. 또한 초당 약 200,000 개의 메시지를 처리하고 오버 헤드가 적 으면 (순차 화 없음) - 초당 약 500,000 개가 발생합니다. - Maxim T
  • 제작자 / 소비자 패턴을 구현하는 동안 ThreadPool 기아를 극복하기 위해 Task.Yield를 사용하는 것은 좋지 않습니다. 이유에 대해 자세히 알고 싶다면 별도의 질문을하는 것이 좋습니다. - noseratio
  • @noseratio는 너처럼 들린다. :)stackoverflow.com/questions/53263258/… - Marc Gravell

연결된 질문


관련된 질문

최근 질문