C# async 和 await 理解 先假設如下場景: 主函數 Main,迴圈等待用戶輸入; 計算函數 Cal,耗時計算大量數據; 為了在Main函數中調用Cal函數,同時Cal函數不阻塞主函數的迴圈,此時需要考慮增加一個CalAsync函數使Cal函數非同步執行。 傳統的思維方法 在CalAsyn ...
C# async 和 await 理解
先假設如下場景:
主函數 Main,迴圈等待用戶輸入;
計算函數 Cal,耗時計算大量數據;
class Test
{
static int Main(string[] args)
{
while(true)
{
// 等待用戶輸入
}
}
public static int Cal() {
int sum = 0;
for (int i = 0; i < 999; i++)
{
sum = sum + i;
}
Console.WriteLine($"sum={sum}");
return sum;
}
}
為了在Main函數中調用Cal函數,同時Cal函數不阻塞主函數的迴圈,此時需要考慮增加一個CalAsync函數使Cal函數非同步執行。
傳統的思維方法 在CalAsync函數中啟動一個線程,併在線程中執行Cal函數:
// using System.Threading;
public static CalAsync()
{
Thread td = new Thread(new ThreadStart(Cal));
td.start();
}
這種方法顯示地創建了一個線程並啟動執行,CalAsync函數本身還是在主線程執行並且無法直接獲取Cal函數的結果。
async 和 await 非同步編程方法 使用async標記CalAsync函數,併在CalAsync函數中創建任務Task非同步執行Cal函數,同時使用await標記獲取Cal函數的執行結果:
// using System.Threading.Tasks;
public static aysnc void CalAsync()
{
int result = await Task.Run(new Func<int>(Cal));
// 或使用lambda書寫方式
// int result = await Task.Run(() => test());
Console.WriteLine(result);
}
在Main函數中直接調用CalAsync函數,可以發現CalAsync成功調用了Cal函數併在一段時間後輸出了結果,同時Main函數並不會被阻塞。
分別在Main、Cal、CalAsync函數中增加代碼列印當前線程ID:
class Test
{
static void Main(string[] args)
{
string tid = Thread.CurrentThread.ManagedThreadId.ToString();
Console.WriteLine($"Main1 tid {tid}");
Task<int> t = CalAsync();
Console.WriteLine($"Main after CalAsync");
Console.Read();
}
public static int Cal()
{
string tid = Thread.CurrentThread.ManagedThreadId.ToString();
Console.WriteLine($"Cal tid {tid}");
int sum = 0;
for (int i = 0; i < 999; i++)
{
sum = sum + i;
}
Console.WriteLine($"sum={sum}");
return sum;
}
public static async Task<int> CalAsync()
{
string tid = Thread.CurrentThread.ManagedThreadId.ToString();
Console.WriteLine($"CalAsync1 tid {tid}");
int result = await Task.Run(new Func<int>(Cal));
tid = Thread.CurrentThread.ManagedThreadId.ToString();
Console.WriteLine($"CalAsync2 tid {tid}, result={result}");
return result;
}
}
結果如圖:
可以看出,在CalAsync函數中,await標記之前,代碼在主線程中執行,而await標記之後,代碼在子線程中執行。
理解與結論:
-
在C#中, async標記了一個包含非同步執行的函數,通過async標記的函數若在主線程中直接調用,則函數一開始仍在主線程中執行;
-
aysnc標記的函數內部必須包含await標記需要非同步執行的函數(根據vs2017編譯提示),若當前函數在主線程中直接調用,則await標記前的代碼在主線程中執行,await標記後的代碼在其非同步子線程中執行;
-
async標記的函數返回值必須為void、Task、Task< TResult> 類型,可以理解為async標記的函數返回的是 “空”、“即將執行的任務”、“帶結果的即將執行的任務”實例;
-
async標記的函數可以繼續往下調用async標記函數,調用形式如下例, 從調用邏輯可以理解為await實際上用來觸發所標記的Task任務非同步執行,並最後獲取非同步執行的返回值,從運行過程看該觸發應該僅對最終的Task任務有效:
public static async Task<int> CallCalAsync()
{
string tid = Thread.CurrentThread.ManagedThreadId.ToString();
Console.WriteLine($"CallCalAsync1 tid {tid}");
int result = await CalAsync();
tid = Thread.CurrentThread.ManagedThreadId.ToString();
Console.WriteLine($"CallCalAsync2 tid {tid}, result={result}");
return result;
}
總結
C#中async與await非同步編程,可以理解為:
1. async聲明瞭一個包含非同步執行代碼的函數,該函數執行時不會阻塞調用線程;
2. await存在於async函數中,聲明瞭一個非同步執行入口,程式動態運行時從該入口創建併進入一個非同步線程環境,併在該線程執行任務實例及任務實例返回之後的代碼;
3. 一個async函數中聲明多個await關鍵字時,程式將代碼順序創建併進入非同步子線程執行任務實例及任務實例返回之後的代碼直到下一個await聲明處, 最後一個await聲明之後的代碼會在最後一個非同步子線程中執行 ;
3. await標記的右側代碼返回或定義了一個任務實例,該實例由需要非同步執行的目標耗時函數初始化,併在最終定義處觸發非同步執行。