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?
In order to prevent stack overflow bugs, you must have a base case where the function stops make new recursive calls. If there is no base case then the function calls will never stop and eventually a stack overflow will occur. Here is an example of a recursive function with a base case.
The most-common cause of stack overflow is excessively deep or infinite recursion, in which a function calls itself so many times that the space needed to store the variables and information associated with each call is more than can fit on the stack.
Explanation: Recursions are always managed by using stack. 3. Which of these will happen if recursive method does not have a base case? Explanation: If a recursive method does not have a base case then an infinite loop occurs which results in Stack Overflow.
Top-level code, up to and including the first await expression (if there is one), is run synchronously. In this way, an async function without an await expression will run synchronously. If there is an await expression inside the function body, however, the async function will always complete asynchronously.
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.
Reason why it overflows is because overflows stack.
10 static async Task CheckAsync(TimeSpan recursiveTimer)
20 {
30 // do some work
40 await Task.Delay(recursiveTimer);
50 CheckAsync(recursiveTimer);
60 }
Code execution would go
10 20 30 40 50 60 //CheckAsync(recursiveTimer)
10 20 30 40 50 60 //CheckAsync(CheckAsync(recursiveTimer))
10 20 30 40 50 60 //CheckAsync(CheckAsync(CheckAsync(recursiveTimer))
Contents of that method should be something like:
while(doCheck){
await Task.Delay(recursiveTimer);
CheckAsync(recursiveTimer);
}
Assuming you want to wait after every async call and do this while doCheck is true. I assume that you change doCheck value in another thread.
Take a look at : Best Practices in Asynchronous Programming
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