若你正在觀看此篇文章,那麼你將會對於 為什麼需要使用非同步程式設計,真的可以提升整體應用程式的執行效能嗎? 問題更有興趣的。
深度研究 : C# 平行 / 並行計算 Parallel.For 隱藏在細節背後的惡魔,你所不瞭解的平行與併行計算
今天,想來談談絕大部分的 C# 開發者都會產生這樣的不正確的 C# 程式碼寫法與用法,那就是當我們採用 工作式非同步模式 (TAP, Task-based Asynchronous Pattern) 進行非同步的程式開發的時候,並且想要寫個非同步運作的分法,在很多時候,您會想說,底下的方法,就是一個非同步運作的程式碼方法。
了解更多關於 [使用 async 和 await 進行非同步程式設計] 的使用方式
了解更多關於 [Thread Class] 的使用方式
了解更多關於 [Task Class] 的使用方式
在這裡,我們在方法 M1 前面使用了 async 修飾詞,不過,這個方法並沒有任何物件值需要回傳,因此,在底下的程式碼中,您看到了這個方法使用了
async void
。
在工作式非同步模式的方法中,回傳值的類型僅有 Task / Task<T>,前者代表沒有任何實際物件要回傳,而後者表示要回傳型別為 T 的物件;不過,那我們為什麼又可以使用 void 這個關鍵字呢?通常,我們會在綁定事件的方法上,需要使用
async void
這樣的標示,這是因為這些事件的函式簽章就是僅支援 void 這樣的回傳值,另外,我們在這個綁定的事件方法中,又需要撰寫工作式非同步模式的功能,因此,又需要在這個事件方法前面,加入 async
這個修飾詞。所以,除了是在綁定事件的方法內,其他任何情況下,對於您開發的工作式非同步模式方法,就不需要使用
async void這樣的回傳值標示。
上面的說明,很重要,很重要,很重要
public async void M1()
{
var foo = DateTime.UtcNow.ToString("mm:ss.ffff");
Console.WriteLine($"M1 開始時間 : {foo}");
await Task.Delay(3000);
foo = DateTime.UtcNow.ToString("mm:ss.ffff");
Console.WriteLine($"M1 結束時間 : {foo}");
}
現在,讓我們來看看這個方法實際使用的情況,首先,我們定義一個類別 A,它的定義如下程式碼,其中, M1 是我們使用
async void
的方式建立的方法,而 M2,則是正常的工作式非同步模式方法的程式碼寫法。class A
{
public async void M1()
{
var foo = DateTime.UtcNow.ToString("mm:ss.ffff");
Console.WriteLine($"M1 開始時間 : {foo}");
await Task.Delay(3000);
foo = DateTime.UtcNow.ToString("mm:ss.ffff");
Console.WriteLine($"M1 結束時間 : {foo}");
}
public async Task M2()
{
var foo = DateTime.UtcNow.ToString("mm:ss.ffff");
Console.WriteLine($"M2 開始時間 : {foo}");
await Task.Delay(3000);
foo = DateTime.UtcNow.ToString("mm:ss.ffff");
Console.WriteLine($"M2 結束時間 : {foo}");
}
}
M1這個方法,會先顯示這個方法的開始執行時間,接著,便會暫停3秒鐘,接著便會顯示結束執行這個方法的時間。
在測試程式碼中,我們先建立一個類別A的物件 objA,接著呼叫 objA.M1() 方法,最後,等候使用按下任一按鍵。
class Program
{
static async Task Main(string[] args)
{
A objA = new A();
objA.M1();
Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
Console.ReadKey();
}
}
上面的程式碼執行結果如底下所示,你可以看到,當呼叫 M1 方法之後,接著, M1 方法似乎就以另外一個執行緒的方式進行執行,所以, Main 這個方法內的程式碼,沒有等到 M1 執行完畢,就直接執行
objA.M1()
之後的所有敘述,而當三秒鐘之後,就會顯示 M1 方法已經結束的時間。
這是因為我們使用了
async void
的方式來宣告這個方法,因此,我們沒有任何方法,可以等候 M1 方法執行完成之後,才要繼續執行 objA.M1()
之後的所有敘述。M1 開始時間 : 09:23.1819
Press any key to Exist...
M1 結束時間 : 09:26.2171
現在,讓我們修改測試程式碼,我們在呼叫 M1 方法前後,與在 M1 方法內,當要呼叫
await Task.Delay(3000);
的前後,都顯示出當時執行緒的 ID (這裡顯示的是受管理的執行緒ID) Thread.CurrentThread.ManagedThreadId
。class A
{
public async void M1()
{
var foo = DateTime.UtcNow.ToString("mm:ss.ffff");
Console.WriteLine($"M1 開始時間 : {foo}");
Console.WriteLine($"M1 {Thread.CurrentThread.ManagedThreadId}");
await Task.Delay(3000);
Console.WriteLine($"M1 {Thread.CurrentThread.ManagedThreadId}");
foo = DateTime.UtcNow.ToString("mm:ss.ffff");
Console.WriteLine($"M1 結束時間 : {foo}");
}
public async Task M2()
{
var foo = DateTime.UtcNow.ToString("mm:ss.ffff");
Console.WriteLine($"M2 開始時間 : {foo}");
await Task.Delay(3000);
foo = DateTime.UtcNow.ToString("mm:ss.ffff");
Console.WriteLine($"M2 結束時間 : {foo}");
}
}
class Program
{
static async Task Main(string[] args)
{
A objA = new A();
Console.WriteLine($"Main {Thread.CurrentThread.ManagedThreadId}");
objA.M1();
Console.WriteLine($"Main {Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
Console.ReadKey();
}
}
此時,執行結果將會如下所示,您會看到,當 M1 的方法內,其
await Task.Delay(3000);
敘述執行完畢之後,此時的執行緒 ID 變成了 4。Main 1
M1 開始時間 : 20:55.2604
M1 1
Main 1
Press any key to Exist...
M1 4
M1 結束時間 : 20:58.2897
最後,讓我們加入 M2 的方法呼叫,對於要呼叫 M2 的非同步方法,我們使用了
await
關鍵字 await objA.M2();
。class A
{
public async void M1()
{
var foo = DateTime.UtcNow.ToString("mm:ss.ffff");
Console.WriteLine($"M1 開始時間 : {foo}");
Console.WriteLine($"M1 {Thread.CurrentThread.ManagedThreadId}");
await Task.Delay(3000);
Console.WriteLine($"M1 {Thread.CurrentThread.ManagedThreadId}");
foo = DateTime.UtcNow.ToString("mm:ss.ffff");
Console.WriteLine($"M1 結束時間 : {foo}");
}
public async Task M2()
{
var foo = DateTime.UtcNow.ToString("mm:ss.ffff");
Console.WriteLine($"M2 開始時間 : {foo}");
await Task.Delay(1000);
foo = DateTime.UtcNow.ToString("mm:ss.ffff");
Console.WriteLine($"M2 結束時間 : {foo}");
}
}
class Program
{
static async Task Main(string[] args)
{
A objA = new A();
Console.WriteLine($"Main {Thread.CurrentThread.ManagedThreadId}");
objA.M1();
await objA.M2();
Console.WriteLine($"Main {Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
Console.ReadKey();
}
}
底下將為這個測試程式碼的執行輸出結果,我們可以看到, objA.M1() 方法一執行到
await Task.Delay(3000);
敘述之後,就立即返回到 Main 方法內,接著執行 await objA.M2();
,不過,此時,整個程式將會等候到 objA.M2() 方法執行完後,才會繼續進行下去,而在這個時候, M1 的方法,也持續在等候 Task.Delay 的甦醒時間ain 1
M1 開始時間 : 24:53.7485
M1 1
M2 開始時間 : 24:53.7645
M2 結束時間 : 24:54.7670
Main 4
Press any key to Exist...
M1 5
M1 結束時間 : 24:56.7678