顯示具有 Task 標籤的文章。 顯示所有文章
顯示具有 Task 標籤的文章。 顯示所有文章

2021年4月3日 星期六

2021年04月 .NET 平行程式設計介紹 免費課程

2021年04月 .NET 平行程式設計介紹 免費課程

.NET 平行程式設計介紹 免費課程

課程名稱

.NET 平行程式設計介紹

課程介紹

平行程式設計是很早以前就存在的一個設計方式,隨著電腦處理器數量不斷的增加,更加凸顯這門技術的重要性,也是絕大部分程式設計師夢幻的開發技術,它可以提升整體應用程式的執行效能與改善流暢反應的使用者體驗;不過,平行程式設計也存在著以難以理解與複雜設計方法而著名,讓許多程式設計師無法輕鬆駕馭這個設計方法。

在這個技術分享課程中,首先會透過一個範例專案,讓大家體會到非同步/平行技術所帶來的好處與想要解決的問題,緊接著將會帶領大家一窺 .NET 開發環境中的各種非同步程式設計技術有哪些設計模式,並且了解 .NET 提供了可以用於平行計算的原理與相關 API;這些內容將會透過一個使用同步設計的專案來開始,逐步使用各種 .NET 提供的非同步/平行設計技術,明瞭到如何使用這些非同步/平行開發技術來完成同樣使用同步方式所設計的專案程式碼,並且知道整體上改善了多少執行效能。

透過本課程的介紹,可以程式開發人員具備了非同步/平行相關知識,與實際操作這些程式碼開如何設計出來,當然,也能夠發現到整體的應用程式執行效能與總處理量徹底的大幅提升與設計出具有流暢反應應用程式。

本課程將會涵蓋底下主題

同步 Synchronous 與非同步 Asynchronous 程式設計、平行 Parallelism 與並行 Concurrency 計算、執行緒 Thread、多執行緒、執行緒集區 ThreadPool、工作 Task 程式設計、用 APM、EAP、TAP 非同程式設計、用 async await 做出以同步方式來設計非同步應用需求、常見早期非同步程式設計 Timer BackgroundWorker、平行資料 Parallel.For Parallel.Foreach、平行查詢 PLINQ 處理程式設計

參加條件

  • 具備 C# 程式語言開發經驗
  • 了解泛型、委派、Lambda的使用方式
  • 擁有基本電腦架構運作知識
  • 對多執行緒開發有興趣
  • 能夠熱情參與課程互動
  • 具有 .NET Core 開發環境(VS4W / VS Code / VS4M 皆可)

報名方式

  • 請先使用該 表單 填寫報名資訊與對課程期望
  • 或者掃描底下 QRCode,填寫

202104 .NET 平行程式設計介紹_ 課程報名申請表

  • 請注意,填寫送出本問卷,並不代表可以參加此課程,因為座位有限,若可以參加本免費課程者,將會透過 FB 私訊通知也會於 FB  Xamarin Blazor 實驗室  公告相關訊息

開課時間

2021.04.24 (六) 下午 13:00 ~ 16:00

上課地點

高雄市鼓山區大順一路439號6樓之3 地圖

(凹子底捷運站1號出口,在大順路與博愛路的彰化銀行樓上)

課程費用

免費

其他說明

  • 本課程使用的程式語言為 .NET / C#
  • 預計時間約為3小時
  • 若報名無故沒來參加,將會禁止日後參加本單位舉辦的相關課程
  • 請自行攜帶可以開發與執行 .NET Core 或者 .NET Framework 筆電,進行現場實作練習
  • 該課程以互動提問、討論方式設計,若無法適應此教學方式,請勿報名參加
  • 若有上網需求,請自備可以透過電腦聯網的行動裝置
  • 本課程不提供上課簡報檔案

 




2020年11月16日 星期一

C# 用最快的速度完成他,不考慮CPU記憶體,完成 10000 工作單元,你所不瞭解的多執行緒計

C# 用最快的速度完成他,不考慮CPU記憶體,完成 10000 工作單元,你所不瞭解的多執行緒計算

這兩天看到臉書社團上有篇討論文章,那就是提問的人提出一個問題:用最快的速度完成他,不考慮CPU記憶體 同時執行 10000 次

這裡原先是要透過底下的程式碼,產生出10000個併行工作單元,並且使用 Parallel 類別 提供的Parallel.For 方法來同時執行這些工作單元,在每個委派方法內,使用休息5秒的做法,模擬需要執行的處理時間,而提問的人遇到瓶頸,希望採用 用最快的速度完成他,不考慮CPU記憶體 的方式來解決此一問題。

Parallel.For(0, 10000, (i) =>
{
Thread.Sleep(5 * 1000);
});

不知道大家是否有看到提問人提出的這個簡單又明瞭的訴求 用最快的速度完成他,不考慮CPU記憶體,又姑且不論大家有著許多額外的建議與批評,這包括討論到 Thread 與 Task 是否有不同、有差異嗎?Thread.Sleep 也是浪費那條thread、thread 新增太慢、您應該先考慮有沒有了解 Thread 的意思、你要先搞清楚你要處理的事件是CPU密集型任務還是IO密集型任務,task本質上只是在同一個時間可以做更多事,不會加快處理事件的時間、這種程式我一輩子都不會寫到也不會遇到有這種需求,請問你能得到甚麼等等。

首先,我想要先針對提問人的需求,不管有著潛在問題或者後遺症,先來看看是否能夠做到且滿足他的需求,那就是同時啟動10000並行工作單元,能否在 5 秒內完成。

先使用最基本的 C# Thread 執行緒類別,看看能否做到同時執行10000個相同的工作,並且在五秒左右同時完成,底下是採用的程式碼

int MAX = 10000;
int SLEEP = 5 * 1000;
List<Thread> threads = new List<Thread>();
CountdownEvent cde = new CountdownEvent(MAX);
Console.WriteLine($"starting {MAX} threads...");
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < MAX; i++)
{
    int idx = i;
    Thread thread = new Thread(x =>
    {
        Thread.Sleep(SLEEP);
        cde.Signal();
    });
    thread.IsBackground = true;
    threads.Add(thread);
}
 
 
for (int i = 0; i < MAX; i++)
{
    threads[i].Start();
}
 
cde.Wait();
stopwatch.Stop();
Console.WriteLine();
Console.WriteLine($"{stopwatch.ElapsedMilliseconds} ms");

這裡透過 for 迴圈,產生出 10000 個執行緒物件,並且設定這些執行緒都是背景執行緒,這是可選擇性的作法,當然也可以產生 10000 個前景執行緒;為了要能夠確認這 10000 個執行是否都執行完畢,在此使用了 CountdownEvent 類別,根據這篇文章 提到 : System.Threading.CountdownEvent 是一個同步處理基本類型,在發出了特定次數的訊號給它之後,就會解除封鎖其等候中的執行緒。所以,就使用了 new CountdownEvent(MAX) 來進行 10000 次的倒數計時,只要執行緒執行完成之後,便會透過 cde.Signal(); 敘述,送出訊號,這樣就會完成倒數加一的工作。

再透過另外一個迴圈,將這些執行緒一次全部啟動執行,因此,理論上這台電腦中將會有 10000 個同時執行的工作,在此迴圈之後,使用 cde.Wait() 方法來等待 10000 委派方法的執行完成,因為這裡有使用 Stopwatch 類別 要來量測整個大量同時執行的工作花費了多少時間,最後便會顯示出總共執行時間大約是多少。

這裡將會是分別執行 3 次的輸出結果

starting 10000 threads...

6239 ms

starting 10000 threads...

6198 ms

starting 10000 threads...

6480 ms

先使用最基本的 C# Task 工作類別,看看能否做到同時執行10000個相同的工作,並且在五秒左右同時完成,底下是採用的程式碼

int MAX = 10000;
int SLEEP = 5 * 1000;
List<Task> tasks = new List<Task>();
Console.WriteLine($"starting {MAX} tasks...");
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < MAX; i++)
{
    int idx = i;
    Task task = Task.Factory.StartNew(() =>
      {
          Thread.Sleep(SLEEP);
      }, TaskCreationOptions.LongRunning);
    tasks.Add(task);
}
Task.WaitAll(tasks.ToArray());
stopwatch.Stop();
Console.WriteLine();
Console.WriteLine($"{stopwatch.ElapsedMilliseconds} ms");

這裡透過 for 迴圈,產生出 10000 個 Task 物件,不過,這裡使用了 TaskFactory.StartNew 方法 來產生這些大量工作物件,原因很簡單,因為想要這些大量的工作物件,不需要透過 執行緒集區 來取得執行緒,而是要透過直接建立一個新的執行緒來處理該工作所指派的委派程式碼

(聽起來也些模糊、有些詭異、有些迷糊,反過頭來說,若你看懂了這段話,其實,這篇文章的問題你也就會解了;另外,許多人,甚至自視為大神的人,似乎對於執行緒與工作間的差異與本質不同,存在著許多問題,請大家在學習或者觀看網路文章的時候,要多多 停、看、聽)

當啟動完成 10000 個工作之後,便使用了 Task.WaitAll 方法 來等待全部的 10000 個工作都完成。

(從這裡,聰明的你應該已經看得出 Thread 執行緒 與 Task 工作 之間的差異點了嗎?但是,你可以分辨與看得出相同點嗎?)

這裡有使用 Stopwatch 類別 要來量測整個大量同時執行的工作花費了多少時間,最後便會顯示出總共執行時間大約是多少。

這裡將會是分別執行 3 次的輸出結果

starting 10000 tasks...

6550 ms

starting 10000 tasks...

6547 ms

starting 10000 tasks...

6533 ms

說明到這裡,你應該也看得出來 Thread 執行緒 與 Task 工作 之間的相同點了嗎?其中一個是,不論是使用執行緒,或者工作來同時執行 10000 工作單元,每個工作單元預計約 5 秒鐘執行時間,而整個執行完成的時間大約是 6.1~6.6 秒,這應該與原提問人想要做到的目標有些接近吧~

若對於這裡所提到的內容,歡迎大家在這裡進行討論,看看大家是否可以推敲出問題在哪裡,畢竟

名偵探柯南最常說的一句話 : 真相只有一個

請繼續參考更精采的 C# 平行 / 並行計算 Parallel.For 隱藏在細節背後的惡魔,你所不瞭解的平行與併行計算

彩蛋

上面兩種做法為單純僅使用執行序,或者工作來滿足這個題目的需求,不過,不論是哪種作法,都可以看到要耗損將近 10000 個執行序來完成這個需求任務。

在此,稍微修改一下原先 Task 的程式碼如下

int MAX = 10000;
int SLEEP = 5 * 1000;
List<Task> tasks = new List<Task>();
Console.WriteLine($"starting {MAX} tasks...");
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < MAX; i++)
{
    int idx = i;
    Task task = Task.Run(async () =>
    {
        await Task.Delay(SLEEP);
    });
    tasks.Add(task);
}
Task.WaitAll(tasks.ToArray());
stopwatch.Stop();
Console.WriteLine();
Console.WriteLine($"{stopwatch.ElapsedMilliseconds} ms");

這裡將會是分別執行 3 次的輸出結果

starting 10000 tasks...

5024 ms

starting 10000 tasks...

5035 ms

starting 10000 tasks...

5036 ms

甚麼?上面的程式碼幾乎與前面採用 TaskFactory.StartNew 方法 的做法大致相同,但是,為什麼這樣的寫法卻更接近 5 秒的時間,幾乎是 5.0xx 秒左右。

到這裡,讀者您應該更能夠了解到直接使用執行緒或工作來強制產生大量執行緒所帶來的後遺症與副作用,之前有聽到某位自稱大神說過,想要讓程式跑的更快,就要使用更多的執行緒,殊不知嗎啡可用於幫人麻醉的緩解疼痛藥品,但是長期經常服用,而不知道嗎啡具有成癮性,將會形成吸食毒品問題與造成身體器官發生問題;用多了執行緒,到時候會很麻煩地。隨意聽信偏方、江湖術士的話,受騙的將會是你自己,因此,唯有對於整個基本知識與運作方式的徹底明瞭,才會有助於這接高階技術的學習與未來進行除錯與思考的依據。

炸(詐)彈

好的,大部分的看完這篇文章之後,再度回到原先的問題

那就是提問的人提出一個問題:用最快的速度完成他,不考慮CPU記憶體 同時執行 10000 次,這裡指名 使用 Parallel 類別 提供的Parallel.For 方法 來完成

Parallel.For(0, 10000, (i) =>
{
Thread.Sleep(5 * 1000);
});

也就想說,沒問題,我也會解決此一問題,那就是把原先的 Thread.Sleep(5 * 1000); 敘述,改成 await Task.Delay(5 * 1000); 那不就好了。而且許多大神也都是這麼順利成章的說,想要使用非同步處理,就直接使用 Parallel.For 方法 就可以做到了(也許你已經成為歐陽鋒,而所學成的九陰真經是黃蓉瞎掰給你的,最後結果是如何,要你自己去看那本書),現在也是驗證這些人說明的時候。

Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
Parallel.For(0, 10000, async (i) =>
{
    //Thread.Sleep(5 * 1000);
    await Task.Delay(5 * 1000);
});
stopwatch.Stop();
Console.WriteLine();
Console.WriteLine($"{stopwatch.ElapsedMilliseconds} ms");

OK OK 很 OK ,那就開始執行吧,將會得到底下的結果


23 ms

xxxx.exe (處理序 37520) 已結束,出現代碼 0。
按任意鍵關閉此視窗…

一看到執行結果,不要以為你練成神功了,若以剛剛的例子,還沒五秒鐘,只花費了 23 ms ,整個程式就結束執行了,這樣似乎與之前使用 Thread.Sleep 方法有些不同,因為,在這個例子中,程式一結束,那 10000 個等候 5 秒的工作單元 Unit of Work 在還沒執行完成前,也就直接提前終止執行了。

這樣的結果不是所預期的,因此,再度修改程式碼,使用執行緒同步 CountdownEvent 類別 來同時等待這 10000 個工作單元的完成時刻來臨。

CountdownEvent cde = new CountdownEvent(10000);
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
Parallel.For(0, 10000, async (i) =>
{
    //Thread.Sleep(5 * 1000);
    await Task.Delay(5 * 1000);
    cde.Signal();
});
 
cde.Wait();
stopwatch.Stop();
Console.WriteLine();
Console.WriteLine($"{stopwatch.ElapsedMilliseconds} ms");

稍做小小修正,完成上述程式碼,二話不囉嗦,再來執行3次,得到底下結果


5039 ms

xxxx.exe (處理序 37520) 已結束,出現代碼 0。

5043 ms

xxxx.exe (處理序 37520) 已結束,出現代碼 0。

5034 ms

xxxx.exe (處理序 37520) 已結束,出現代碼 0。

首先,整個處理程序 Process 沒有提早 5 秒鐘前就結束,接著,竟然使用 Parallel.For 方法 也可以做到僅需要 5 秒鐘就可以完成 10000 個工作單元的預期目標,現在,觀看這篇文章的讀者能夠知道發生了甚麼問題嗎?

若對於這裡所看到的各種疑問,歡迎大家在這裡進行討論,看看大家是否可以推敲出問題在哪裡,畢竟

名偵探柯南最常說的一句話 : 真相只有一個

請繼續參考更精采的

C# 平行 / 並行計算 Parallel.For 隱藏在細節背後的惡魔,你所不瞭解的平行與併行計算

Blazor實戰故事經驗分享 1 - 風起雲湧 如何從無到有建立Blazor團隊與採用全端開發方式設計出給上市企業使用的Web系統

Blazor實戰故事經驗分享 2 - 風雲再現 探究 Blazor 可以快速開發出來內部細節



2019年9月9日 星期一

C# 當要呼叫多個連續非同步作業的各種不同設計方式

C# 當要呼叫多個連續非同步作業的各種不同設計方式

當要進行非同步程式設計的時候,尤其是要採用 TPL / TAP 方式來進行程式碼設計,將會享受到 Task 類別所帶來的許多好處,因為,Task 類別已經將許多非同步程式設計的需求抽象化了,並且可以使用 Task 類別進行非同步作業的設計。在這篇文章中將要討論的是:當有兩個以上的非同步作業需要處理,例如 非同步作業A / 非同步作業B,而這裡需要先完成 非同步作業A 之後,才能夠繼續完成 非同步作業B;這個時候,將會有多種設計方式選擇可以完成這樣的需求,因此,在這裡將會來描述這些不同多個非同步作業的設計方式。

了解更多關於 [使用 async 和 await 進行非同步程式設計] 的使用方式
了解更多關於 [Task Class] 的使用方式



對於整個測試環境的模擬將會是:非同步作業A 將會需要從遠端 Web API 上取得回傳結果內容,接著,非同步作業B 需要進一步地把資料寫入到本機磁碟目錄下,因此,可以知道需要等非同步作業A完成之後,才能夠繼續非同步作業B 的運行,而不是同時將 非同步作業A 與 非同步作業B 一起執行,這樣會造成兩個非同步作業沒有同步。
另外,為了要模擬出呼叫 Web API & 寫入檔案的動作,都需要使用非同步作業的方式,因此,請使用 .NET Core 建立一個 .NET Core Console 類型的專案,填入這裡的程式碼範例,實際體驗執行看看。
在這篇文章所提到的專案原始碼,可以從 GitHub 下載
這篇文章所用的程式碼範例,將會在這篇文章的最後面會列出來。

使用工作 ContinueWith

首先要看到的是使用 Task.ContinueWith 方法來將兩個接續的非同步工作連結在一起,這裡寫法的特色:使用一行敘述 (Task 支援 fluent API 寫法),就可以完成所有設計,這似乎是很吸引人的一個要素,不過,來看看要怎麼做到呢?
C Sharp / C#
private static void 使用工作ContinueWith()
{
    string url = "https://lobworkshop.azurewebsites.net/api/RemoteSource/AddASync/15/43/3000";
    var myTask = new HttpClient().
        GetStringAsync(url).ContinueWith(task1 =>
        {
            string content = task1.Result;
            File.WriteAllTextAsync("MyFileContinueWith.txt", content).
            ContinueWith(task2 =>
            {
                if (task2.Status == TaskStatus.RanToCompletion)
                {
                    Console.WriteLine("已經成功下載內容並且寫入到檔案內");
                }
                else
                {
                    Console.WriteLine("寫入檔案發生了問題");
                }
            });
        });

    Thread.Sleep(4000);
}
從上面的程式碼可以看的出來
  • 首先建立一個 HttpClient 物件
  • 接著呼叫 HttpClient 執行個體的 GetStringAsync() 方法,這個方法將會回傳一個 Task<string> 的物件
  • 因此,可以接續這個 Task<string> 物件來使用 ContinueWith 方法,設定這個非同步工作的 callback 回呼事件,也就是設定當這個非同步工作完成之後 (對於非同步工作的完成,將可能會有不同的最後結果狀態,例如:也許是正常完成且有得到結果,或者是執行非同步作業過程中,有拋出例外異常,造成沒有完成這個非同步作業,又或者是這個非同步作業被呼叫端指定要取消該非同步作業的執行),將會來指定所要執行的一個委派方法。
  • 對於 ContinueWith 的委派方法,將會需要有一個 Task<string> 的參數的函式簽章,因此,在這裡使用 Lambda 匿名委派方法來設計,透過傳入的 task1 引數,可以得知上一個非同步工作的執行結果,例如 可以使用 Task.Status 屬性或者是 Task.IsCompleted 、 Task.IsFaulted 、 Task.IsCanceled 。
  • 為了簡化練習,這裡將不做呼叫遠端 Web API 執行結果狀態的檢查,而是繼續使用 File.WriteAllTextAsync() 方法來將資料寫入到本機檔案內
  • 而這個方法將會回傳一個 Task 物件,有了這個物件,就可以使用 ContinueWith 方法來指定這個非同步作業的 callback 委派事件的訂閱
  • 在 File.WriteAllTextAsync 非同步作業的 ContinueWith 委派方法內,將會使用傳入的 task2 引數物件,檢查寫入檔案的結果是否已經正常且成功結束了,若有,會顯示一段文字來說明這個狀態,否則,將會顯示出一段錯誤訊息
  • 整個單一 C# 表示式將會得到一個 myTask 物件,在這裡可以等候這個非同步工作完成或者像是在這裡,故意讓主執行緒休息 5 秒鐘,讓非同步作業完成執行。
透過 ContinueWith 是可以完成這樣的連鎖接續非同步作業的設計,因為採用 callback 技術,將會得到一個好處,這裡僅需要使用一行敘述就可以完成這樣的設計,當然,這也是一個缺點,因為整個程式碼將會變成很難閱讀與維護,整個接續非同步作業的程式碼都完全綁定在一起,是想,若有一個需求是要先完成非同步作業A,接著要完成非同步作業B,然後又要完成非同步作業C,你便可以想像到這個一個 C# 敘述將會變成多麼的複雜,這還不包括每次非同步作業完成之後,還需要確認非同步作業的完成狀態是甚麼,依據這些不同狀態來進行相對應的處理機制的程式碼設計。

同步封鎖等待非同步作業完成

當然,對於進行程式碼設計的時候,若採用同步程式設計的方式,所撰寫出來的程式碼將會具有比較好的閱讀性,因為,程式碼式一行接著一行來執行,不過,想要使用同步程式設計方式,設計出具有非同步執行能力的程式碼,透過 Task 類別所提供的功能是可以做到的;這樣太完美了,設計出具有非同步執行能力的程式碼,但是,採用同步程式碼撰寫風格,便可以滿足需求與撰寫出比較容易閱讀與好維護的程式碼。
現在來看看使用同步封鎖的方式,進行這樣的設計方式。
在 同步封鎖等待非同步作業完成() 方法內,將會使用同步封鎖等待的方式,等待非同步作業完成,然後才要接續接下來的程式碼執行,整段程式碼將會以同步程式設計的方式來進行撰寫,不過,當要進行執行非同步作業的時候,對於呼叫端 Client 將會使用封鎖本身執行緒的方式(該 Client 端執行緒在此時無法執行任何程式碼),一直等候到非同步作業完成。
同樣的呼叫 Web API 與 將取得結果寫入到檔案的需求,使用底下的方式來設計,是不是程式碼變得容易閱讀與除錯了呢?
不過,這樣的設計方式還存在一個缺點,那就是當要等待非同步執行完成的時候,本身執行緒將會被封鎖住,無法執行其他的工作;接下來的第三種方式,將會使用 C# 5.0 提供的 async 修飾詞 與 await 運算子來解決此一問題。
C Sharp / C#
private static void 同步封鎖等待非同步作業完成()
{
    string url = "https://lobworkshop.azurewebsites.net/api/RemoteSource/AddASync/15/43/3000";
    string content = new HttpClient().GetStringAsync(url).Result;
    var task2 = File.WriteAllTextAsync("同步封鎖等待完成.txt", content);
    task2.Wait();
    if (task2.Status == TaskStatus.RanToCompletion)
    {
        Console.WriteLine("已經成功下載內容並且寫入到檔案內");
    }
    else
    {
        Console.WriteLine("寫入檔案發生了問題");
    }
}

await 呼叫非同步方法

第三種設計作法將要改善會造成封鎖呼叫端執行緒的問題,此時,你的開發環境需要使用 C# 5.0 / .NET Framework 4.5 以上的版本才能夠做到,並且設計成為 await呼叫非同步方法Async() 這個方法。
  • 首先,將呼叫遠端 Web API 的程式碼,包裝在一個 非同步方法 ( async Method ) 內,這裡將會設計出 GetRemoteStringAsync() 方法,在此方法內,將會使用 await 運算子 來等待 new HttpClient().GetStringAsync(url) 非同步作業完成;await 運算子 與 wait() 不同在於,後者會造成當前執行緒進入封鎖等候狀態,而前者將會因為非同步作業尚未完成,而會先記住當前執行狀態內容和執行位置,接著直接返回到呼叫 GetRemoteStringAsync() 方法的呼叫端上,當 new HttpClient().GetStringAsync(url) 非同步工作完成之後,將會透過會還原剛剛提到的執行狀態內容與當時離開的程式執行位置,接續 await 之後的程式碼繼續來執行;這些動作將會由編譯器來產生出相關的程式碼,以便可以做到這樣的描述動作。
  • 因此,當 new HttpClient().GetStringAsync(url) 非同步作業完成之後,透過 await 運算子 將會取得該非同步方法回傳的呼叫 Web API 的字串內容,在此將會儲存到 content 區域變數內
  • 緊接著將將會要將 content 內容寫入到檔案內,在這裡將會使用 await 運算子呼叫 WriteRemoteStringAsync("MyFile.txt", content) 非同步方法,同樣的,若非同步方法尚未執行完成,則會立即返回到呼叫端;一旦非同步方法執行完畢之後,將會回到 await 之後來繼續執行。
對於採用 async 修飾詞 與 await 運算子關鍵字的方式將不會造成程式碼進入封鎖等候的狀態,而且,整個程式碼設計邏輯將都是採用同步程式設計的邏輯來進行,只要適當地加入 async / await ,你的程式碼就具有非同步執行的能力與效果,所以,整體的程式碼將會變成比較好閱讀與維護。
C Sharp / C#
private static async Task await呼叫非同步方法Async()
{
    string content = await GetRemoteStringAsync();
    await WriteRemoteStringAsync("MyFile.txt", content);
}

static async Task<string> GetRemoteStringAsync()
{
    string url = "https://lobworkshop.azurewebsites.net/api/RemoteSource/AddASync/15/43/3000";
    string result = await new HttpClient().GetStringAsync(url);
    return result;
}
static async Task WriteRemoteStringAsync(string filename, string content)
{
    await File.WriteAllTextAsync(filename, content);
}

本篇文章中的所有程式碼

C Sharp / C#
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("這裡將會展示當要呼叫多個非同步作業的各種不同設計方式");

        // 這裡寫法的特色:使用一行敘述 (Task 支援 fluent API 寫法),就可以完成所有設計
        //使用工作ContinueWith();

        // 這裡寫法的特色:需要使用封鎖式等待非同步作業(可能是非同步工作或者非同步方法)的完成
        同步封鎖等待非同步作業完成();

        // 這裡寫法的特色:使用 async 修飾詞 與 await 運算子 ,採用不會直接封鎖的方式來呼叫非同步作業
        // 重點是可以使用同步設計的風格和方式,設計出具有非同步運作能力的程式碼
        //await呼叫非同步方法()Async.Wait();
    }

    private static void 同步封鎖等待非同步作業完成()
    {
        string url = "https://lobworkshop.azurewebsites.net/api/RemoteSource/AddASync/15/43/3000";
        string content = new HttpClient().GetStringAsync(url).Result;
        var task2 = File.WriteAllTextAsync("同步封鎖等待完成.txt", content);
        task2.Wait();
        if (task2.Status == TaskStatus.RanToCompletion)
        {
            Console.WriteLine("已經成功下載內容並且寫入到檔案內");
        }
        else
        {
            Console.WriteLine("寫入檔案發生了問題");
        }
    }

    private static void 使用工作ContinueWith()
    {
        string url = "https://lobworkshop.azurewebsites.net/api/RemoteSource/AddASync/15/43/3000";
        var myTask = new HttpClient().
            GetStringAsync(url).ContinueWith(task1 =>
            {
                string content = task1.Result;
                File.WriteAllTextAsync("MyFileContinueWith.txt", content).
                ContinueWith(task2 =>
                {
                    if (task2.Status == TaskStatus.RanToCompletion)
                    {
                        Console.WriteLine("已經成功下載內容並且寫入到檔案內");
                    }
                    else
                    {
                        Console.WriteLine("寫入檔案發生了問題");
                    }
                });
            });

        Thread.Sleep(4000);
    }

    private static async Task await呼叫非同步方法Async()
    {
        string content = await GetRemoteStringAsync();
        await WriteRemoteStringAsync("MyFile.txt", content);
    }

    static async Task<string> GetRemoteStringAsync()
    {
        string url = "https://lobworkshop.azurewebsites.net/api/RemoteSource/AddASync/15/43/3000";
        string result = await new HttpClient().GetStringAsync(url);
        return result;
    }
    static async Task WriteRemoteStringAsync(string filename, string content)
    {
        await File.WriteAllTextAsync(filename, content);
    }
}


了解更多關於 [使用 async 和 await 進行非同步程式設計] 的使用方式
了解更多關於 [Task Class] 的使用方式