前段時間, 拿到一個框架, 之前也沒怎么看, 只記得里面使用了蠻多的異步.
public async Task<ActionResult> Login(LoginModel model, string returnUrl)
之前的項目中, 沒有使用過異步的. 可能有人會把多線程和異步混為一談, 其實還是不一樣的東西.
那么, 今天就先來學習下異步, 以備使用之需. 這里只介紹新的方式了, 至于之前老的方式, 有些復雜, 沒有新方式直觀, 簡潔.
一. 知識點
異步方法:提供了一種簡便方式完成可能需要長時間運行的工作,而不必阻止調用方的線程。 異步方法的調用方可以繼續工作,而不必等待異步方法完成。
await:運算符應用于一個異步方法的任務掛起方法的執行,直到等待任務完成。 任務表示正在進行的工作。 await 表達式不阻止它在其上執行的線程。
async: async 修飾符指示方法、它進行修改 lambda 表達式或 匿名方法 是異步的
Task類:它表示一個任務,在.net4.5版本開始被支持, 它隸屬于 System.Threading.Tasks命名空間下;通過Task類可以方便的開啟一個新的線程。
二、小Demo
1.mvc文件下載
在mvc源碼解析的時候, 不知道我有沒有提過, mvc在默認模式下, 是使用的異步方式來完成工作的. 那先來看一個mvc里面的應用吧
public class HomeController : Controller { public async Task<FileResult> DownLoad(string name) { var path = Server.MapPath("~/FileRes/") + name; using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, FileOptions.Asynchronous)) { byte[] data = new byte[fs.Length]; await fs.ReadAsync(data, 0, data.Length); return File(data, "application/octet-stream", name); } } }
這里讀取文件, 是以異步的方式來讀取的, 當讀取完成之后, 就會將fs關掉, 所以在返回文件的時候, 并不能使用 return File(fs,"application/octet-stream",name)的方式去返回了.
2. 同步方法調用異步方法, 以及異步方法調用異步方法
static void Main(string[] args) { Console.WriteLine("Main Start : " + Thread.CurrentThread.ManagedThreadId); Step1(); Step2(); Console.WriteLine("Main End : " + Thread.CurrentThread.ManagedThreadId); Console.ReadKey(); } static async void Step1() { Console.WriteLine("Step1 start : " + Thread.CurrentThread.ManagedThreadId); try { await Task.Run(() => { Console.WriteLine("Step1.1 Current sleeping ThreadID : " + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(3000); }); await Task.Run(() => { Console.WriteLine("Step1.2 Current sleeping ThreadID : " + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(3000); Console.WriteLine("ThreadTest.Test Runing : " + Thread.CurrentThread.ManagedThreadId); }); } catch (Exception ex) { Console.WriteLine("ThreadTest : " + ex.Message); } await Step3(); Console.WriteLine("Step1 end : " +Thread.CurrentThread.ManagedThreadId); } static void Step2() { Console.WriteLine("Step2 start : " + Thread.CurrentThread.ManagedThreadId); Console.WriteLine("Step2 Current ThreadID : " + Thread.CurrentThread.ManagedThreadId); Console.WriteLine("Step2 end : " + Thread.CurrentThread.ManagedThreadId); } static async Task Step3() { Console.WriteLine("Step3 start : " + Thread.CurrentThread.ManagedThreadId); await Task.Run(() => { Console.WriteLine("Step3 Current sleeping ThreadID : " + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(5000); }); Console.WriteLine("Step3 end : " + Thread.CurrentThread.ManagedThreadId); }
Main方法調用方法Step2是同步方法調用同步方法, 調用Step1是同步方法調用異步方法.
Step1方法調用Step3方法, 是異步方法調用異步方法.
運行結果:
這里我運行了好幾次, 從上面的結果, 可以看出以下幾點:
1. 主線程全程沒有被阻塞, 一直執行到結束.
如果我在Step2方法中, 加上一句 : Thread.Sleep(10), 那么主線程就會被阻塞在這里, 等待10s中的時間. 啥事都不用干的感覺, 真好.
2. 主線程在Step1方法中, 碰到await等待的時候, 完全沒理會, 直接回到Main方法中, 去執行Step2方法了.
這里就是同步方法調用異步方法時要注意的地方, 因為同步方法不會理await的, 并不會乖乖的在這里等await后面的方法執行結束, 還有一點, await下面的方法, 主程序也不會去管了.
3. 注意到這里的Step1.1和Step1.2的執行線程, 有時候相同, 有時候不同. 這是為什么呢?
這些線程應該都是從線程池中取的, 在異步方法中, 如果當前正在使用的線程已經不需要用了, 會把他還給線程池, 由線程池再分配出去, 給別的線程用. 當休息時間到了之后, 會再向線程池請求線程來完成下面的工作. 有點像打車, 到了一個地方之后, 下車了, 就把出租車這個資源釋放掉了, 等一個小時之后, 要回去了, 再打一個車, 這時候的出租車, 可能是之前打的那輛, 當然也可能不是. 這樣做的一個好處, 就是能增加吞吐量, 提升并發處理能力. 這里出租車如果一直在這里等你的話, 你是不需要付錢的, 賺的少了, 出租車可不怎么愿意了, 哈哈.
4. Step1調用Step3的時候, 很明顯的異步方法調用異步方法, 那么他會不會像之前的同步方法調用異步方法那樣呢?
從上面的圖中, 就能很清晰的看到, 并不是一樣的. 效果上, 像同步方法調用同步方法那樣, 等在這里, 一直等你到天荒地老.
猜想:
如果從計算機原理上來分析這里的異步掛起切換線程, 我猜想過程應該是這樣子的:
當程序運行到sleep之前的時候, 會將數據什么的存入到存儲器和寄存器, 碰到sleep的時候, 就會釋放線程, 開啟定時器, 定時器到點觸發, 通知線程池, 我這邊需要一條線程來繼續處理, 此時線程池收到消息, 會去池中查看哪些線程可用, 如果都沒有, 會先等待一會, 可能是0.5s吧, 如果還沒有可用的線程釋放出來, 就會創建一條線程, 來滿足要求, 完成工作.
參考:
文章列表