我对await
关键字的工作原理有一个脆弱的掌握,我想扩展我对它的理解。
仍然让我头晕目眩的问题是递归的使用。这是一个例子:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TestingAwaitOverflow
{
class Program
{
static void Main(string[] args)
{
var task = TestAsync(0);
System.Threading.Thread.Sleep(100000);
}
static async Task TestAsync(int count)
{
Console.WriteLine(count);
await TestAsync(count + 1);
}
}
}
这显然抛出了一个StackOverflowException
.
我的理解是因为代码实际上是同步运行的,直到第一个异步操作,之后它返回一个Task
包含异步操作信息的对象。在这种情况下,没有异步操作,因此它只是在它最终会得到一个Task
返回的错误承诺下不断递归。
现在稍微改变一下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TestingAwaitOverflow
{
class Program
{
static void Main(string[] args)
{
var task = TestAsync(0);
System.Threading.Thread.Sleep(100000);
}
static async Task TestAsync(int count)
{
await Task.Run(() => Console.WriteLine(count));
await TestAsync(count + 1);
}
}
}
这个不扔StackOverflowException
。我可以理解它为什么起作用,但我更倾向于将其称为直觉(它可能涉及如何安排代码使用回调来避免构建堆栈,但我无法将这种直觉转化为解释)
所以我有两个问题:
- 第二批代码如何避免a
StackOverflowException
? - 第二批代码会不会浪费其他资源?(例如,它是否在堆上分配了大量的 Task 对象?)
谢谢!