异步编程
前段时间, 拿到一个框架, 之前也没怎么看, 只记得里面使用了蛮多的异步.
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吧, 如果还没有可用的线程释放出来, 就会创建一条线程, 来满足要求, 完成工作.
参考: