C#使用Task實現異步方法
一、async和await特性的結構
1. 異步和同步
同步方法:如果一個方法被調用瞭,等待其執行所有處理後調用方法才繼續執行的方法。
異步方法:異步方法能在處理完成之前就回到調用方法。
2.async和await
調用方法:該方法調用異步方法,然後在異步方法(可能在相同的線程,也可能在不同的線程)執行其任務時繼續執行。
異步(async)方法:該方法異步執行其工作,被調用時立即回到調用方法。
await表達式:用於異步方法內部,指明需要異步執行的任務。一個異步方法需要至少包含一個await表達式。
二、什麼是異步方法
1.異步方法的結構
異步方法在完成其工作之前即返回到調用方法,然後在調用方法繼續執行的時候完成其工作。
- 方法頭包含async方法修飾符,該修飾符僅標識此方法是一個異步方法。
- 包含至少一個await表達式,表示可以異步完成的任務。
- 必須具備以下三種返回類型中的一種:void、Task、Task<T>。
- 異步方法的參數可以是任意類型,但不能為ref或out。
- 按照約定,異步方法的名稱應該為Async為後綴。
- 除瞭方法以外,Lambda表達式和匿名方法也可以作為異步對象。
2.異步方法三種返回類型理解
1. Task<T> :如果調用方法要從調用中獲取一個T類型的值,異步方法的返回類型就必須是Task<T>。調用方法通過讀取Task的Result屬性來獲取這個T類型的值。
// 使用返回Task<int>對象的異步方法代碼示例: static class DoAsyncStuff { public static async Task<int> CalculateSumAsync(int i1, int i2) { int sum = await Task.Run(() => GetSum(i1, i2)); return sum;//註意返回的是一個int類型 } private static int GetSum(int i1, int i2) { return i1 + i2; } } internal class Program { static void Main(string[] args) { Task<int> task = DoAsyncStuff.CalculateSumAsync(1, 2); //處理其他事情... Console.WriteLine("Value:{0}",task.Result);//調用方法通過Result獲取這個int類型的值 } }
2.Task : 如果調用方法不需要從異步方法中返回某個值,但需要檢查異步方法的狀態,那麼異步方法可以返回一個Task類型的對象,這時即使異步方法中出現瞭return語句,也不會返回任何東西。
//使用Task不返回類型的異步方法代碼示例: static class DoAsyncStuff { public static async Task CalculateSumAsync(int i1, int i2) { int sum = await Task.Run(() => GetSum(i1, i2)); Console.WriteLine("Value:{0}",sum); } private static int GetSum(int i1, int i2) { return i1 + i2; } } internal class Program { static void Main(string[] args) { Task task = DoAsyncStuff.CalculateSumAsync(1, 2); //處理其他事情... task.Wait(); Console.WriteLine("異步方法結束"); } }
3. void :如果調用方法僅僅想執行異步方法,而不需要與它進行進一步交互時【稱為“調用並忘記”】,異步方法可以返回void類型。
//使用“調用並忘記”的異步方法的代碼示例: static class DoAsyncStuff { public static async void CalculateSumAsync(int i1, int i2) { int sum = await Task.Run(() => GetSum(i1, i2)); Console.WriteLine("Value:{0}",sum); } private static int GetSum(int i1, int i2) { return i1 + i2; } } internal class Program { static void Main(string[] args) { DoAsyncStuff.CalculateSumAsync(1, 2); //處理其他事情... Thread.Sleep(200); Console.WriteLine("Program Exiting"); } }
3.異步方法的控制流
1. 異步方法的結構包含三個不同的區域:
控制流闡述:
- 調用異步方法後,知道遇到第一個await表達式之前都是同步的。
- 遇到await表達式之後,異步方法將控制返回到調用方法。如果方法的返回類型為Task或Task<T>類型,將創建一個Task對象,表示需異步完成的任務和後續,然後將Task返回到調用方法。
- 到目前為止,已經出現兩個控制流,異步方法內的和調用方法內的。異步方法的代碼完成以下工作:
- 異步執行await表達式的空閑任務。
- 當await表達式完成時,執行後續部分。如果後續還有await表達式,將重復此步驟:異步執行await表達式,然後執行後續部分。
- 當後續部分遇到return語句或到達方法末尾時:
- 如果方法返回類型為void,控制流將退出。
- 如果方法返回類型為Task,後續部分設置Task的屬性並退出。
- 如果方法返回類型為Task<T>,後續部分還將設置Task的Result屬性。
- 同時調用方法中的代碼繼續其進程,從異步方法獲取Task對象。當需要其實際值時,就引用Task對象的Result屬性,屆時,如果異步方法設置瞭該屬性,調用方法就能調用其值並繼續,否則將暫停等待該屬性被設置,然後再繼續執行。
需要註意的是:異步方法的return語句並沒有真正返回一個值,它隻是退出瞭,異步方法中的返回類型始終是方法頭中聲明的返回類型。
三、await表達式
await表達式指定瞭一個異步執行的任務。這個任務可能是一個Task類型的對象,也可能不是,默認情況下這個任務在當前線程異步運行。
我們可能需要編寫自己的方法作為await表達式的任務。最簡單的方式是在你的方法中使用Task.Run創建一個Task。(關於Task.Run即在不同的線程上運行你的方法)Task.Run方法有8個重載 ,如下圖所示:
以Func<int> 委托作為參數的代碼示例:
class MyClass { public int Get10() { return 10; } public async Task DoWorkAsync() { //方式1:使用Get10創建名為ten的Func<int>委托,將該委托傳入Task.Run方法 Func<int> ten = new Func<int>(Get10); int a = await Task.Run(ten); //方式2:直接在Task.Run中創建委托,傳入Get10方法 int b = await Task.Run(new Func<int>(Get10)); //方式3:使用與Func<T>兼容的Lambda表達式,Lambda表達式將隱式轉換為該委托 int c = await Task.Run(() => { return 10; }); Console.WriteLine("{0},{1},{2}",a,b,c); } } internal class Program { static void Main(string[] args) { Task task = new MyClass().DoWorkAsync(); task.Wait(); } }
從Task.Run的重載中可以發現,可以作為Run中第一個參數的委托有四種:
使用四種委托作為參數的Run方法代碼示例:
class MyClass { public static async Task DoWorkAsync() { await Task.Run(() => Console.WriteLine(5.ToString()));//Aciton,無參無返 Console.WriteLine((await Task.Run(() => 6)).ToString());//TResult Func(),無參有返,返回TResult類型對象 await Task.Run(() => Task.Run(() => Console.WriteLine(7.ToString())));//Task Func(),無參有返,返回簡單Task對象 Console.WriteLine((await Task.Run(() => Task.Run(() => 8))).ToString());//Task<TResult> Func(),無參有返,返回Task<T>對象 } } internal class Program { static void Main(string[] args) { Task task = MyClass.DoWorkAsync(); task.Wait(); } }
假如我們需要一個接受參數的方法,但是無法匹配上述的四種委托(接受沒有參數的方法的委托),我們可以創建一個Lambda表達式,其唯一行為就是運行我們的接受參數的方法,來滿足Run方法所能接受的委托形式。代碼示例如下:
class MyClass { //需要使用異步的方法 public static int GetSum(int a, int b) { return a + b; } public static async Task DoWorkAsync(int a,int b) { int result = await Task.Run(() => GetSum(a, b));//創建一個滿足Func<TResult>委托形式的Lambda表達式 Console.WriteLine(result); } } internal class Program { static void Main(string[] args) { Task task = MyClass.DoWorkAsync(6,7); task.Wait(); } }
四、取消一個異步操作
一些.NET異步方法允許你請求終止執行,我們可以在自己的異步方法中加入這個特性。有兩個類:CancellationToken和CancellationTokenSource是專門為瞭取消異步操作設計的。
1. CancellationToken對象包含瞭一個任務是否應被取消的消息。擁有CancellationToken對象的任務需要定期檢查其令牌(token)的狀態,如果CancellationToken對象中的IsCancellationRequested屬性為true,任務需停止其操作並返回。CancellationToken是不可逆的,也就是一旦IsCancellationRequested設置為true就不能更改瞭。
2. CancellationTokenSource對象創建可分配給不同任務的CancellationToken對象。任何持有CancellationTokenSource的對象都可以調用其Cancel方法,這會將CancellationToken對象中的IsCancellationRequested設置為true。
3. 使用這兩個取消類的代碼:
class MyClass { public async Task RunAsync(CancellationToken ct) { if (ct.IsCancellationRequested) return; await Task.Run(()=>CycleMethod(ct)); } void CycleMethod(CancellationToken ct) { Console.WriteLine("開始CycleMethod方法"); const int max = 5; for (int i = 0; i < max; i++) { if (ct.IsCancellationRequested) return; Thread.Sleep(1000); Console.WriteLine("完成:{0}/{1}",i+1,max); } } } internal class Program { static void Main(string[] args) { CancellationTokenSource cts = new CancellationTokenSource(); CancellationToken ct = cts.Token; MyClass mc = new MyClass(); Task t = mc.RunAsync(ct); /* 取消異步操作的過程是協同的: 即調用CancellationTokenSource的Cancel時,它本身並不會執行取消操作 而是會將CancellationToken的IsCancellationRequested屬性設置為true 包含CancellationToken的代碼負責檢查該屬性,並判斷是否需要停止執行並返回 */ //Thread.Sleep(3000); //cts.Cancel();//解除註釋將產生取消異步的操作 t.Wait(); Console.WriteLine("是否取消瞭異步方法:{0}", ct.IsCancellationRequested); } }
五、異常處理的await表達式
就使用try…catch…正常處理異常就行。
class MyClass { public static async Task BadAsync() { try { await Task.Run(() => { throw new Exception(); }); }catch (Exception ex) { Console.WriteLine("異步中拋出異常..."); } } } internal class Program { static void Main(string[] args) { Task task = MyClass.BadAsync(); task.Wait(); Console.WriteLine("Task Status:{0}",task.Status);//Task Status:RanToCompletion,因為Task沒有被取消 Console.WriteLine("Task IsFaulted:{0}",task.IsFaulted);//Task IsFaulted:False,因為沒有未處理的異常 } }
六、在調用方法中同步地等待任務
1. Wait
Task的Wait方法可以讓調用方法等待異步方法完成,在Wait方法處,流程是阻塞的。
class MyClass { public void PrintA() { const int max = 10; for (int i = 0; i < max; i++) { Thread.Sleep(1000); Console.WriteLine("A任務完成:{0}/{1}", i + 1, max); } } public async Task ForWaitAsync() { await Task.Run(new Action(PrintA)); } } internal class Program { static void Main(string[] args) { MyClass mc = new MyClass(); Task t = mc.ForWaitAsync(); Thread.Sleep(6000);//主線程休眠6秒 //等待任務完成,使用此方法可以讓主方法等待異步方法完成,否則主方法休眠六秒結束時異步方法也將直接結束 t.Wait(); } }
2. WaitAll和WaitAny
Wait是等待單一任務完成,我們也可以使用WaitAll和WaitAny等待多個任務。(這兩個方法是同步方法,知道等待條件滿足後再繼續執行,不然流程會阻塞在那裡)
class MyClass { public void PrintA() { const int max = 10; for (int i = 0; i < max; i++) { Thread.Sleep(1000); Console.WriteLine("A任務完成:{0}/{1}", i + 1, max); } Console.WriteLine("A任務全部完成!"); } public void PrintB() { const int max = 5; for (int i = 0; i < max; i++) { Thread.Sleep(1000); Console.WriteLine("B任務完成:{0}/{1}", i + 1, max); } Console.WriteLine("B任務全部完成!"); } public async Task AForWaitAsync() { await Task.Run(new Action(PrintA)); } public async Task BForWaitAsync() { await Task.Run(new Action(PrintB)); } } internal class Program { static void Main(string[] args) { MyClass mc = new MyClass(); Task a = mc.AForWaitAsync(); Task b = mc.BForWaitAsync(); Task[] task = new Task[] { a, b };//存放Task的數組 Thread.Sleep(8000);//調用方法休眠8秒 //Task.WaitAll(task);//等待所有任務完成 Task.WaitAny(task);//等待任意一個任務完成後將不再阻塞 } }
WaitAll和WaitAny分別還包含四個重載:
七、在異步方法中異步地等待任務
有時在異步方法中,你會希望用await表達式來等待Task。這時異步方法會返回到調用方法,但該異步方法會等待一個或所有任務完成。可以通過Task.WhenAll或Task.WhenAny方法來實現。這兩個方法稱為組合子。
class MyClass { public void PrintA() { const int max = 10; for (int i = 0; i < max; i++) { Thread.Sleep(1000); Console.WriteLine("A任務完成:{0}/{1}", i + 1, max); } } public void PrintB() { const int max = 5; for (int i = 0; i < max; i++) { Thread.Sleep(1000); Console.WriteLine("B任務完成:{0}/{1}", i + 1, max); } } public async Task AAsync() { await Task.Run(new Action(PrintA)); } public async Task BAsync() { await Task.Run(new Action(PrintB)); } public async Task ForWhenAsync() { Task a = AAsync(); Task b = BAsync(); Task[] tasks = { a, b };//Task數組 //await Task.WhenAll(tasks);//在異步中等待所有任務 await Task.WhenAny(tasks);//在異步中等待任意一個任務 //此異步方法中的流程會阻塞在Task.WhenAny(tasks)中,直到某個條件滿足才會到達這裡 Console.WriteLine("現在任務A是否完成:{0}",a.IsCompleted?"Yes":"No"); Console.WriteLine("現在任務B是否完成:{0}",b.IsCompleted?"Yes":"No"); } } internal class Program { static void Main(string[] args) { const int max = 12; MyClass mc = new MyClass(); mc.ForWhenAsync(); for (int i = 0;i < max ; i++) { Thread.Sleep(1000); Console.WriteLine("Main任務完成:{0}/{1}", i + 1, max); } } }
八、Task.Delay方法
Task.Delay方法創建一個對象,該對象將暫停其在線程中的處理,並在一定時間之後完成。和Thread.Sleep阻塞線程不同的是,Task.Delay方法不會阻塞線程,線程可以繼續處理其他工作。
class Simple { Stopwatch sw = new Stopwatch(); public void DoRun() { Console.WriteLine("調用之前"); ShowDelayAsync(); Console.WriteLine("調用之後"); } private async void ShowDelayAsync() { sw.Start(); Console.WriteLine("Delay 執行之前:{0}",sw.ElapsedMilliseconds); await Task.Delay(1000);//創建瞭一個Task對象,該對象將暫停其在線程中的處理,所以在打印下一句話之前,控制回到瞭調用方法打印調用方法中的"調用之後" Console.WriteLine("Delay 執行之後:{0}", sw.ElapsedMilliseconds); } } internal class Program { static void Main(string[] args) { Simple simple = new Simple(); simple.DoRun(); Console.Read(); } }
到此這篇關於C#使用Task實現異步方法的文章就介紹到這瞭,更多相關C# Task異步內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- c# Task.Wait()與awaiat Task異常處理的區別說明
- C#中async和await的深入分析
- C#中的Task.Delay()和Thread.Sleep()區別(代碼案例)
- C#代碼延時的幾種實現
- .Net中Task Parallel Library的基本用法