非同步
TAP
C# 非同步程式設計概述
2019/11/05 22:45:47
6
3028
在沒有非同步的程式設計裡,同步的程式執行都是使用同一個執行緒並且由上而下的順序在執行,例如:
一個人要做一份早餐,早餐菜單上有烤吐司夾火腿蛋,並且配上一杯咖啡,同步的程式會這樣寫
static void Main(string[] args)
{
var watch = Stopwatch.StartNew();
Console.WriteLine("開始早餐製作...");
平底鍋預熱();
烤吐司();
煎火腿蛋();
抹果醬();
倒咖啡();
吐司夾火腿蛋();
擺盤完成開始吃早餐();
watch.Stop();
Console.WriteLine($"同步烹煮早餐共花費:{watch.Elapsed.Seconds} 秒");
Console.ReadKey();
}
static void 平底鍋預熱()
{
Console.WriteLine($"預熱平底鍋 5 秒鐘 [Thread ID:{Thread.CurrentThread.ManagedThreadId}]");
Thread.Sleep(5000);
Console.WriteLine($"預熱完成");
}
static void 烤吐司()
{
Console.WriteLine($"烤吐司 10 秒鐘 [Thread ID:{Thread.CurrentThread.ManagedThreadId}]");
Thread.Sleep(10000);
Console.WriteLine($"烤吐司完成");
}
static void 煎火腿蛋()
{
Console.WriteLine($"煎火腿蛋 20 秒鐘 [Thread ID:{Thread.CurrentThread.ManagedThreadId}]");
Thread.Sleep(20000);
Console.WriteLine($"煎火腿蛋完成");
}
static void 抹果醬()
{
Console.WriteLine($"抹果醬需要 2 秒鐘 [Thread ID:{Thread.CurrentThread.ManagedThreadId}]");
Thread.Sleep(2000);
Console.WriteLine($"抹果醬完成");
}
static void 倒咖啡()
{
Console.WriteLine($"倒咖啡需要 5 秒鐘 [Thread ID:{Thread.CurrentThread.ManagedThreadId}]");
Thread.Sleep(5000);
Console.WriteLine($"倒咖啡完成");
}
static void 吐司夾火腿蛋()
{
Console.WriteLine($"吐司夾火腿蛋需要 3 秒鐘 [Thread ID:{Thread.CurrentThread.ManagedThreadId}]");
Thread.Sleep(3000);
Console.WriteLine($"吐司火腿蛋完成");
}
static void 擺盤完成開始吃早餐()
{
Console.WriteLine($"擺盤上桌需要 2 秒鐘 [Thread ID:{Thread.CurrentThread.ManagedThreadId}]");
Thread.Sleep(2000);
Console.WriteLine($"早餐可以吃了");
}
同步的程式執行結果如下
執行完後我們發現每,因為都是使用同一個執行緒,所以每個步驟都必須執行完後,才能進到下一個步驟去,因此做這份早餐,總共花費了 47秒。
但現實生活中,我們做早餐並不會一個步驟做完後才會做下一個步驟, 例如: 當平底鍋在預熱的時候,會同時把吐司丟進烤箱烤,
鍋子預熱好了,把火腿蛋下鍋煎,這時候吐司已經烤好的話,會先把吐司抹上果醬後,再回頭把火腿蛋煎好起煎夾進吐司,
倒好咖啡擺上盤子,便可以享用這份豐富的早餐。
由上所描述做早餐同時會做多個步驟,就是一種非同步的表現。
在把上面程式改成非同步的方式前,先來看看幾個非同步程式的觀念。
什麼是非同步程式設計
- 一種並行 Concurrent 處理
- 當呼叫非同步方法之後,將會立即返回 (return) 呼叫端
- 承諾未來將一定會完成這個方法並且得到結果
- 不一定需要透過執行緒才能夠做到非同步作業
非同步有四個重要名詞
- APM (Asynchronous Programming Model) 非同步程式設計模型
- EAP (Event-based Asynchronous Pattern) 事件架構非同步模式
- TPL (Task Parallel Library) 工作平行程式庫
- TAP (Task-based Asynchronous Pattern) 以工作為基礎的非同步模式
非同步開發的歷史
- .NET Framework 1.1 - 以 APM 進行非同步開發
- .NET Framework 2.0/3.5 - 以 EAP 在不同執行緒上執行作業
- .NET Framework 4.0 - 以 TAP 進行以工作為基礎的非同步開發
- .NET Framework 4.5(C# 5.0+) / .NET Core 1.0+ - 透過 async / await 語法糖使用 TAP 非同步模式
什麼是「工作」(Task)
- Task 類別代表單一作業不會傳回值,通常以非同步方式執行
- 最推薦的非同步開發方法
- 使用單一方法表示同步作業啟始和完成
把開頭做早餐的例子使用 Task 方法簡單的讓做早餐這件事有了非同步的效果,並且看看非同步帶了什麼樣的效益。
static void Main(string[] args)
{
var watch = Stopwatch.StartNew();
Console.WriteLine("開始烹煮早餐...");
var 預熱 = Task.Run(() => 平底鍋預熱());
var 香烤吐司 = Task.Run(() => 烤吐司());
預熱.Wait();
var 香煎火腿蛋 = Task.Run(() => 煎火腿蛋());
香烤吐司.Wait();
抹果醬();
倒咖啡();
香煎火腿蛋.Wait();
吐司夾火腿蛋();
擺盤完成開始吃早餐();
watch.Stop();
Console.WriteLine($"非同步烹煮早餐共花費:{watch.Elapsed.Seconds} 秒");
Console.ReadKey();
}
static void 平底鍋預熱()
{
Console.WriteLine($"預熱平底鍋 5 秒鐘 [Thread ID:{Thread.CurrentThread.ManagedThreadId}]");
Thread.Sleep(5000);
Console.WriteLine($"預熱完成");
}
static void 烤吐司()
{
Console.WriteLine($"烤吐司 10 秒鐘 [Thread ID:{Thread.CurrentThread.ManagedThreadId}]");
Thread.Sleep(10000);
Console.WriteLine($"烤吐司完成");
}
static void 煎火腿蛋()
{
Console.WriteLine($"煎火腿蛋 20 秒鐘 [Thread ID:{Thread.CurrentThread.ManagedThreadId}]");
Thread.Sleep(20000);
Console.WriteLine($"煎火腿蛋完成");
}
static void 抹果醬()
{
Console.WriteLine($"抹果醬需要 2 秒鐘 [Thread ID:{Thread.CurrentThread.ManagedThreadId}]");
Thread.Sleep(2000);
Console.WriteLine($"抹果醬完成");
}
static void 倒咖啡()
{
Console.WriteLine($"倒咖啡需要 5 秒鐘 [Thread ID:{Thread.CurrentThread.ManagedThreadId}]");
Thread.Sleep(5000);
Console.WriteLine($"倒咖啡完成");
}
static void 吐司夾火腿蛋()
{
Console.WriteLine($"吐司夾火腿蛋需要 3 秒鐘 [Thread ID:{Thread.CurrentThread.ManagedThreadId}]");
Thread.Sleep(3000);
Console.WriteLine($"吐司火腿蛋完成");
}
static void 擺盤完成開始吃早餐()
{
Console.WriteLine($"擺盤上桌需要 2 秒鐘 [Thread ID:{Thread.CurrentThread.ManagedThreadId}]");
Thread.Sleep(2000);
Console.WriteLine($"早餐可以吃了");
}
執行步驟的寫法不變,但是呼叫步驟方法改用 Task 進行非同步的程式執行。
這次使用了非同步的方式做早餐,在遇到可以非同步進行的程式裡,可以看到使用了不同的執行緒在進行,
因此只花費了30秒的時間,幾乎一樣的程式,但執行的結果卻相差 17 秒之多,這就是非同步的魅力。