新學.net,最近用到了ABP框架,發現了以下代碼:api
public override async Task<UserDto> Get(EntityDto<long> input) { var user = await base.Get(input); var userRoles = await _userManager.GetRolesAsync(user.Id); user.Roles = userRoles.Select(ur => ur).ToArray(); return user; }
看到後,對async , Task , await 徹底不理解,就查閱了相關資料。服務器
簡單說一下個人理解,這3個關鍵字都是多線程機制的一部分,目的是讓處理用戶請求的線程儘快結束這次請求,結束後,就能夠用這個線程繼續接收其餘的請求。多線程
而費時的異步操做,交給後臺線程處理(這裏的後臺線程,應該不能響應用戶請求)。這樣一來,就能讓服務器更快地響應請求。app
Task對象中封裝着線程相關的對象,async 和 await 都是爲Task服務。 想在函數中使用await,就必須聲明函數爲async的。這是由於這裏的await,理解爲掛起更爲恰當,當遇到await後,所在的函數執行必須掛起並當即返回主函數,而等待異步處理的結果得出後,繼續執行接下來的代碼。這樣的函數是特殊的,須要一個async來標識。框架
而這裏的Task<UserDto> 能夠理解爲,一個可以返回UserDto結果的,線程任務。Abp外層框架應該會對其進行await形式的調用,把結果返回給前臺。異步
關於await 和 async 的理解,我貼2段代碼,這2段代碼是在搜資料時,從網上找到的,我對其進行了改進,感謝原做者的付出!async
第一段ide
using System; using System.Threading; using System.Threading.Tasks; namespace ConsoleApp2 { class Program { static void Main(string[] args) { Console.WriteLine("Main start-----Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId); Test(); Console.WriteLine("-----Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId); Console.WriteLine("-----Main end:{0}", Thread.CurrentThread.ManagedThreadId); Console.ReadLine(); } static async Task Test() { Console.WriteLine("======Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId); // 方法打上async關鍵字,就能夠用await調用一樣打上async的方法 await GetName(); Console.WriteLine("Middle---------Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId); await GetName2(); Console.WriteLine("##########Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId); } static async Task GetName() { Console.WriteLine("*****Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId); // Delay 方法來自於.net 4.5 // await Task.Delay(3000); // 返回值前面加 async 以後,方法裏面就能夠用await了 await Task.Run(() => { Thread.Sleep(3000); Console.WriteLine("^^^^^^^^Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId); }); Console.WriteLine("$$$$$$$Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId); } static async Task GetName2() { Console.WriteLine("!!!!!Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId); await Task.Run(() => { Thread.Sleep(3000); Console.WriteLine("++++++++Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId); }); Console.WriteLine("~~~~~~Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId); } } }
輸出以下:函數
Main start-----Current Thread Id :1 ======Current Thread Id :1 *****Current Thread Id :1 -----Current Thread Id :1 -----Main end:1 ^^^^^^^^Current Thread Id :3 $$$$$$$Current Thread Id :3 Middle---------Current Thread Id :3 !!!!!Current Thread Id :3 ++++++++Current Thread Id :4 ~~~~~~Current Thread Id :4 ##########Current Thread Id :4
上面這段代碼,能夠明顯地看 代碼運行的線程,await會對被它阻塞的代碼的運行線程產生影響!spa
第二段代碼:
using System; using System.Threading; using System.Threading.Tasks; namespace ConsoleApp1 { class Program { static void Main(string[] args) { Console.WriteLine("我是主線程,線程ID:{0}", Thread.CurrentThread.ManagedThreadId); TestAsync(); Console.WriteLine("-----------------TestAsync 返回--------------------------"); Console.ReadLine(); } //爲了在函數體內使用await,必須寫async,表示這是個異步函數。 //而這裏使用 await 的做用,就是能讓 TestAsync 函數當即返回main函數,去執行main以後的邏輯,而不用等待 await 的task.在await的task執行完畢後,繼續執行當前task。 //對於一個async函數,可使用await 去等待結果返回,也能夠不使用,就像main函數這樣,實現真正的異步效果,直接運行到了函數結尾。 static async Task TestAsync() { Console.WriteLine("調用GetReturnResult()以前,線程ID:{0}。當前時間:{1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); //建立task,但沒有等待 var name = GetReturnResult(); Console.WriteLine("調用GetReturnResult()以後,線程ID:{0}。當前時間:{1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); //會阻塞上層調用的寫法 //Console.WriteLine("獲得GetReturnResult()方法的結果:{0}。當前時間:{1}", name.Result, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); //不會阻塞上層調用的寫法 //這個await 找到最終的那個 新線程await,並 跳過等待結果,直接返回 Console.WriteLine("獲得GetReturnResult()方法的結果:{0}。當前時間:{1}", await name, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); Console.WriteLine("await 後面的邏輯 => TestAsync 最後一句"); } static Task<string> GetReturnResult() { Console.WriteLine("執行Task.Run以前, 線程ID:{0}", Thread.CurrentThread.ManagedThreadId); return Task.Run(() => { Console.WriteLine("並行線程:GetReturnResult()方法裏面線程ID: {0}", Thread.CurrentThread.ManagedThreadId); Console.WriteLine("並行線程:running...."); Thread.Sleep(3000); return "我是返回值"; }); } } }
結果以下:
我是主線程,線程ID:1 調用GetReturnResult()以前,線程ID:1。當前時間:2018-06-28 03:06:32 執行Task.Run以前, 線程ID:1 調用GetReturnResult()以後,線程ID:1。當前時間:2018-06-28 03:06:32 -----------------TestAsync 返回-------------------------- 並行線程:GetReturnResult()方法裏面線程ID: 3 並行線程:running.... 獲得GetReturnResult()方法的結果:我是返回值。當前時間:2018-06-28 03:06:35 await 後面的邏輯 => TestAsync 最後一句
上面這段代碼,能夠看出async 和await 的具體效果: 主函數直接調用一個用async聲明的函數(不在前面加await),運行到async聲明的函數中的await後當即返回主函數,不會阻塞主函數的代碼塊。這個函數返回的task,通常都會和一個正在後臺線程相關。
這裏須要注意一點,先看代碼:
static Task<string> GetReturnResult() { Console.WriteLine("執行Task.Run以前, 線程ID:{0}", Thread.CurrentThread.ManagedThreadId); var task = new Task<string>(() => { Console.WriteLine("並行線程:GetReturnResult()方法裏面線程ID: {0}", Thread.CurrentThread.ManagedThreadId); Console.WriteLine("並行線程:running...."); Thread.Sleep(3000); return "我是返回值"; }); //用new建立的task對象,必須使用start方法開始任務,否則task不會開始,在這裏就會會永久阻塞程序。 task.Start(); return task; /* return Task.Run(() => { Console.WriteLine("並行線程:GetReturnResult()方法裏面線程ID: {0}", Thread.CurrentThread.ManagedThreadId); Console.WriteLine("並行線程:running...."); Thread.Sleep(3000); return "我是返回值"; }); */ }
雖然這個函數咱們僅僅須要Task<string>,可是其實一個task必需要start後,纔可能被啓動執行。而Task.Run 是自動調用start方法的。
這裏再帖一份代碼,是使用owin中間件進行重定向的代碼,其中有3種對task的使用方法:
app.MapWhen((r) => !r.Request.Path.Value.ToLowerInvariant().StartsWith("/api") && !r.Request.Path.Value.ToLowerInvariant().StartsWith("/swagger") && !r.Request.Path.Value.ToLowerInvariant().Equals("/") && !r.Request.Path.Value.ToLowerInvariant().Equals("") , spa => { spa.Run(context => { //first //context.Response.Redirect("/people");
//雖然是同步邏輯,可是須要一個異步結構的返回值,使用Task.FromResult<>能夠構造 //return Task.FromResult<int>(0); //second //return Task.Run(() => context.Response.Redirect("/people")); //third var task = new Task(() => { context.Response.Redirect("/people"); }); //注意,必須手動啓動!!!!!!! task.Start(); return task; }); });