2017-05-20 3 views
3

.NET 비동기/대기 API를 사용하는 동안 호기심에 부 닥쳤습니다. 루프 내부에서 짧은 지연을 추가 할 때까지 루프가 제한 시간으로 사용 된 지연을 무시하고있었습니다. 이게 어떻게 작동합니까? 가장 직관적 인 행동이 아닙니다!루프 내에 짧은 지연을 추가하면 루프가 무기한 반복되지 않습니다. 왜?

전체 프로그램 :

using System; 
using System.Threading.Tasks; 

public class Program 
{ 
    public static void Main(String[] args) 
    { 
     Task.Run(async() => 
     { 
      await Task.WhenAny(Loop(), Task.Delay(TimeSpan.FromSeconds(1))); 
      Console.WriteLine("Timed out!"); 
     }) 
     .Wait(); 
    } 

    public static async Task Loop() 
    { 
     while(true) 
     { 
      // Commenting this out makes the code loop indefinitely! 
      await Task.Delay(TimeSpan.FromMilliseconds(1)); 

      // This doesn't matter. 
      await DoWork(); 
     } 
    } 

    public static async Task DoWork() 
    { 
     await Task.CompletedTask; 
    } 
} 

배경

실제 프로그램이 while(!done)을 가지고 있지만 인해 donetrue로 설정되지 않습니다 버그에. 루프는 많은 await 호출을 만듭니다. Task.WhenAny 호출은 Loop()이 걸리지 않도록하기위한 단위 테스트입니다. 대부분의 경우 버그가 실제로 발생하지만 시간이 지나면 테스트가 중단됩니다. Loop() 방법을 실행 Task.DelayLoop()에서

bool completedOnTime = Task.Run(() => Loop()).Wait(TimeSpan.FromSeconds(1)); 

이 것 start a new thread을 필요로하지 않습니다

권장 해결 방법.

관련 질문

When would I use Task.Yield()?

+1

시간 초과 작업이 완료되면'CancellationToken'을 사용하여'Task Loop()'을 멈추는가? 이 코드는'Task.Loop()'가 결코 멈추지 않을 것이기 때문에 여기에서 우스운 냄새가 난다. 그리고 멈추기 위해 구현 된 분명한 방법이 보이지 않는다. – Svek

+0

사실, WhenAny에서 취소 토큰을 실제로 중지하려면 필요합니다. 그러나 문제는 취소 요청을 발행 할 기회가 없을 것입니다. 이것은 WhenAny가 결코 돌아 오지 않을 것을 기다리고 있기 때문입니다. –

+1

@KonradJamrozik 실제로 문제가 더 올라가고 있습니다. 호출을'var loopTask = Loop();로 옮기면'WaitAny'가 아니라 결코 돌아 오지 않는'Loop'입니다. Task.WhenAny (loopTask, Task.Delay (TimeSpan.FromSeconds (1));)를 기다리고 디버거에서 볼 수 있습니다. –

답변

5

당신이 그냥 실행을 계속 결코 호출자에게 반환 완료 될 경우 작업이 완료되었는지 확인하기 위해 작업 최초로 확인을 기다리고 있습니다 때. 이 때문에 await DoWork();에 대한 호출은 호출하는 메서드로 돌아 가지 않게 할 것이고 메서드에서 동기식으로 계속 진행할 것입니다.당신이 지연을 제거하면

당신은 그 어느 호출자에게 제어를 부여하지 않고 영원히

public static async Task Loop() 
{ 
    while(true) 
    { 
    } 
} 

그래서 루프 의지 루프를 갖는 동등한 있습니다. 이런 상황에서 호출자에게 돌아갈 지 여부를 모르거나 영원히 반복하지 않도록 코드를 다시 작성할 수 있습니다.

public static async Task Loop() 
{ 
    while(true) 
    { 
     var workTask = DoWork(); 
     if(workTask.GetAwaiter().IsCompleted) //This IsCompleted property is the thing that determines if the code will be synchronous. 
      await Task.Yield(); //If we where syncronous force a return here via the yield. 
     await workTask; //We still await the task here in case where where not complete, also to observe any exceptions. 
    } 
} 
3

현재 Loop() 작업 영원히 while(true) 조건 루프 것이다 : 당신은 당신의 루프를 중단하는 CancellationToken 전달 고려해야한다

public static async Task Loop() 
{ 
    while(true) { } // this iteration will never end. 
        // as noted by Scott Chamberlain's answer, the caller will 
        // never regain control of this task 
} 

. 첫 번째 작업이 완료되면, 나머지 작업을 취소할지 여부를 고려

:

public static async Task Loop(CancellationTokenSource cts) 
{ 
    while (cts != null && !cts.IsCancellationRequested) 
    { 
     // your work you want to keep iterating until cancelled 
    } 
} 

은 또한 내 제안에 동의하는 설명하기 위해 this answer에서 차용했습니다. 다른 작업이 취소되지 않았지만 결코 기다려지지 않은 인 경우, 그들은 버려졌습니다. 포기 된 작업은 완료로 실행되며 그 결과는 무시됩니다. 의 예외는 버려진 작업도 무시됩니다.

추가 자료 : Crafting a Task.TimeoutAfter Extension Method

+0

@Svek 부작용 (콘솔이나 파일 쓰기 또는 다른 눈에 보이는 효과)이있을 수있는 작업 취소는 좋은 생각이 아니라고 생각합니다. –

+0

루프 조건이 실제로 "while (! done)"이지만 "done"은 버그로 인해 true로 설정되지 않는다고 가정합니다. WhenAny 시간 초과는 테스트에서 버그가 발생했을 때 테스트가 중단되지 않도록하기 위해 사용됩니다. 그런 시나리오에서 취소 토큰 검사를 추가하면 테스트 코드로 생산 코드를 오염시킬 수 있습니다. ( –

+0

@PaulStelian "부작용"이 없어야합니다. 토큰은 각 반복을 검사하고 루프 내의 모든 문을 중단시키지 않습니다 'method. – Svek

관련 문제