引子
今天遇到一个简单的问题,一个获取下载文件的接口,本来是要在判断文件不存在的情况下重新生成的,但是因为重新生成需要的时间比较长,因此就考虑,当文件不存在的时候开启一个后台线程,而直接返回错误,让重试,这样体验会好一点。
代码如下
string filePath = ""; if (File.Exists(filePath)) return true; Task.Run(() => { // 执行生成文件的操作 }); return false;
这里的Task.Run()就是启动一个后台线程,不会影响主线程,而直接返回失败,这样在用户下一次在点击下载的时候,就可以直接获取到文件了。
开启异步线程(不阻塞线程)的方法
1.使用Thread类
2.使用ThreadPool线程池
3.使用最新的Task
第一种方法已经淘汰,因为这种方法是不会考虑机器的实际情况,会不停的创建线程,最终很容易导致内容溢出;
第二种方法,在Task没有出现的时候经常使用,优点是线程池中会合理管理线程,如果到达上限后,后续创建的线程会进行排队,但是用它来操作线程略显麻烦
第三种是新方法,比较推荐,改善了方法1和2的缺点。
使用方法如下
Task t1 = new Task(()=>{}); t1.Start();
或者
Task.Run(()=> { });
后者相当于将创建的线程直接启动了。
后续执行ContinueWith
可以对后台任务分成若干块,除了第一部分外,以ContinueWith连接,如下
Task.Run(()=> { }).ContinueWith((t)=> { }).ContinueWith((t) => { });
而参数t就是上一步执行的task,可以在ContinueWith方法对上一步的操作结果进行判断,以进行不同的处理
Task的
IsCompleted属性可以判断任务是否执行完成
IsCompletedSuccessfully属性可以判断任务是否有异常
可以放在ContinueWith中用来判断任务的执行情况
线程同步
Task对象使用Wait()方法来实现线程的等待,多个Task则可以使用Task.WaitAll()或者Task.WaitAny()来同步
wait()和await
为了线程同步有时候需要对线程进行等待,有两种方法,如
Task.Run(()=>{}).Wait()
或者
await Task.Run(()=>{})
这两种方法效果类似,都是将线程等待起来,有结果再继续,但是区别在于wait()是同步阻塞,而await是异步阻塞。
也就是说wait()的时候,线程是占用状态,无法释放
而await的时候,线程是释放的,可以用来做其他的工作
一个现象
经常发现一些朋友在写web项目代码的时候,方法中调用了很多await的异步接口,其实这些异步接口的使用并不能提高程序的性能,也就是说不是说一个接口使用同步接口调用了3秒,换成异步接口就变1秒了(去掉await并行执行另说)。
执行效率还是不会变的,只不过程序在遇到await之后将线程释放,以提高网站的吞吐量。
总结
异步和多线程真的相当复杂,这里只是记录了自己使用中的一些经验,肯定有不足和错误的地方,希望见谅
总之在使用异步和多线程的时候,要比单线程程序更加复杂,一些单线程程序中理所应当的东西,放在多线程异步环境下可能会出现大问题,使用的时候必须要小心和谨慎