While using the .NET async/await API I ran into a curiosity: a loop was ignoring a delay used as a timeout, until I added a short delay inside the loop. How does this work? Not the most intuitive behavior!
Full program:
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;
}
}
Background
The actual program has while(!done)
but due to a bug done
is never set to true
. The loop makes many await
calls. The Task.WhenAny
call is in a unit test to prevent Loop()
from hanging. If I introduce a bug on purpose most of the time the test indeed times out, but sometimes it still just hangs.
Suggested workaround that doesn't require Task.Delay
in Loop()
bool completedOnTime = Task.Run(() => Loop()).Wait(TimeSpan.FromSeconds(1));
This will start a new thread executing the Loop()
method.
Related questions
When would I use Task.Yield()?
when you await a Task it first checks to see if the task is complete, if it is complete it just continues the execution and never returns to the caller. Because of this the call to await DoWork();
will never cause you to return to the calling method, it will just synchronously continue on in the method.
When you remove the delay you now have the equivalent of having
public static async Task Loop()
{
while(true)
{
}
}
so the loop will loop forever without ever giving control back to the caller. In situations like this where you don't know if you will be returning to the caller or not and you want to guarantee you don't loop forever you could rewrite your code as
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.
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With