我的await
關鍵字是如何工作的脆弱把握,我想延長我對它的理解有點。遞歸和的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
。我可以sortof明白爲什麼它的工作原理,但我把它更多的是直覺的(它可能與代碼是如何安排使用回調以避免建立堆棧交易,但我不能把這一預感到解說)
所以我有兩個問題:
- 如何第二批代碼,避免
StackOverflowException
? - 第二批代碼是否浪費其他資源? (例如它是否在堆上分配了大量的Task對象?)
謝謝!
因此,如果我用一個立即返回完成的Task對象的函數替換Task.Run(),它會重新引入堆棧溢出異常? (或者我剛剛提出的東西是不可能的?) – riwalk
@ Stargazer712不僅是可能的,它確實會做你正在描述的東西。只需嘗試'等待Task.FromResult
@ Stargazer712是的!如果你用'Task.Yield()'替換它(保證需要發佈一個延續),你將得到保證免於堆棧溢出(以性能成本)。請注意,'Yield'返回的是除Task之外的其他候選項。從任務獲得此保證要困難得多,因爲您必須確保在等待功能查詢時未完成此任務。 – usr