Can I recursively call an async function without overflowing the stack?

Since the return site of the async function isn't the caller, I assume this works, but thought I'd verify that this was safe just in case. If it's not, why would this overflow the stack?

static async Task CheckAsync(TimeSpan recursiveTimer)
{
    // do some work

    await Task.Delay(recursiveTimer);
    CheckAsync(recursiveTimer);
}

Edit: I decided to just try it out - it looks like it does not overflow the stack (it's running on my machine now - it's currently on call 210,000). My assumed reason is that because the return site of the CheckAsync function is not actually CheckAsync, but instead somewhere in the async plumbing. So when CheckAsync calls CheckAsync, it's not actually adding to the call stack via the normal function call mechanism, but instead putting the function as an object on some async "to be executed" queue, which is run via some other thread managing async functions.

To anyone that knows this mechanism well: does this sound about right?

Jon Skeet
people
quotationmark

The reason it's working for you is not because of the way CheckAsync is called, but because you're awaiting the result of Task.Delay. That will always return a "not completed yet" task, so awaiting it will schedule a continuation. That continuation will be fired on an effectively empty stack, so it doesn't matter that you then make the recursive call.

Now, I think you still effectively have a memory leak, because IIRC the framework will be keeping track of a "logical stack" which will get bigger and bigger... but that will be stored on the heap and expand until you run out of memory.

If you want to see the stack blow up, all you need to do is change the code to:

static async Task CheckAsync(TimeSpan recursiveTimer)
{
    // Whatever
    await Task.FromResult(5);
    CheckAsync(recursiveTimer);
}

At that point, assuming the code in "whatever" doesn't await anything, you'll have entirely synchronous code, just using Task for keeping track of completion and exceptions.

I certainly wouldn't recommend this as a pattern for repeatedly doing work (partly due to the memory leak I mentioned), but I hope that explains why you're not getting a stack overflow.

people

See more on this question at Stackoverflow