我试图更好地理解由于编译器在C#中为async-await生成代码而发生的堆分配。
请考虑以下代码:
Array
(
[0] => Array
(
[0] => 08004
[SQLSTATE] => 08004
[1] => 911
[code] => 911
[2] => [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Database 'SAMEORIGIN' does not exist. Make sure that the name is entered correctly.
[message] => [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Database 'SAMEORIGIN' does not exist. Make sure that the name is entered correctly.
)
[1] => Array
(
[0] => 42000
[SQLSTATE] => 42000
[1] => 102
[code] => 102
[2] => [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Incorrect syntax near 'ClickJacking'.
[message] => [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Incorrect syntax near 'ClickJacking'.
)
[2] => Array
(
[0] => 42000
[SQLSTATE] => 42000
[1] => 4145
[code] => 4145
[2] => [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]An expression of non-boolean type specified in a context where a condition is expected, near 'expect'.
[message] => [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]An expression of non-boolean type specified in a context where a condition is expected, near 'expect'.
)
)
这里我们将有3个static async Task OneAsync()
{
Console.WriteLine("OneAsync: Start");
await TwoAsync();
Console.WriteLine("OneAsync: End");
}
static async Task TwoAsync()
{
Console.WriteLine("TwoAsync: Start");
await ThreeAsync();
Console.WriteLine("TwoAsync: End");
}
static async Task ThreeAsync()
{
Console.WriteLine("ThreeAsync: Start");
var c = new HttpClient();
var content = await c.GetStringAsync("http://google.com");
Console.WriteLine("Content:" + content.Substring(0, 10));
Console.WriteLine("ThreeAsync: End");
}
结构类型(一个用于AsyncStateMachine
,一个用于OneAsync
,一个用于TwoAsync
)由编译器生成。
您能否确认我的假设是否正确?
调用ThreeAsync
方法(反过来调用链直到OneAsync
)会导致 3 ThreeAsync
结构类型被置于堆?
如果我在AsyncStateMachine
方法中没有使用HttpClient而只是从它返回ThreeAsync
,则会有2 Task.CompletedTask
个结构类型(一个用于{{1}和AsyncStateMachine
)之一。在这种情况下,当整个调用链同步执行时,没有OneAsync
结构类型的堆分配?
答案 0 :(得分:4)
您可以使用三种状态机,因为有三种异步方法。编译器始终创建的存根创建状态机。如果您将ThreeAsync
更改为没有async
修饰符,则可以使用两个,而只需编写一个返回已完成任务的常规方法。
你对堆分配是正确的:所涉及的等待者在检查时都已完成,因此不需要安排继续。这意味着除了(可能)任务之外,不需要任何堆分配。当您返回普通Task
并且任务总是成功时,我也希望也可以使用缓存的已完成任务。
答案 1 :(得分:2)
基本上:两者都是;生成状态机,但不会在堆上结束,除非它实际上是异步的。在一些性能关键的场景中,大部分呼叫都是同步的,手动实现代码以在同步和异步之间切换可能是有利的:
static Task OneAsync()
{
async Task Awaited(Task t)
{
await t;
Console.WriteLine("OneAsync: End");
}
Console.WriteLine("OneAsync: Start");
var task = TwoAsync();
if (task.Status != TaskStatus.RanToCompletion)
return Awaited(task);
Console.WriteLine("OneAsync: End");
return task; // could also have used Task.CompletedTask
}
请注意,这涉及一些手动复制 - 尤其是在结果之后或之后发生的事情(Console.WriteLine
)。有一些方法可以减少这种情况,通常涉及更多的本地功能。另请注意,task.Status
非常昂贵,并且在可用时(.NET Core或ValueTask<T>
):IsCompletedSuccessfully
应该是首选。