C#多线程TPL模式高级用法探秘
一、引言
我们先来看下面的一个小示例:一个Winfrom程序,界面上有一个按钮,有两个异步方法,点击按钮调用两个异步方法,弹出执行顺序,代码如下:
using System; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace TPLDemoSln{public partial class Form1 : Form{public Form1(){InitializeComponent(); }/// /// 按钮点击事件/// /// /// 【C#多线程TPL模式高级用法探秘】private async void btnStart_Click(object sender, EventArgs e){string i1 = await F1Async(); MessageBox.Show("i1=" + i1); string i2 = await F2Async(); MessageBox.Show("i2=" + i2); }/// /// 异步方法F1/// ///private Task F1Async(){MessageBox.Show("F1 Start"); return Task.Run(() => {// 休眠1秒Thread.Sleep(1000); MessageBox.Show("F1 Run"); return "F1"; }); }/// /// 异步方法F2/// /// private Task F2Async(){MessageBox.Show("F2 Start"); return Task.Run(() =>{// 休眠2秒Thread.Sleep(2000); MessageBox.Show("F2 Run"); return "F2"; }); }}}
在上面的代码中,Task.Run()是用来把一个代码段包装为Task
运行程序,可以得到如下的输出顺序:
F1 Start->F1 Run->i1=F1->F2 Start->F2 Run->i2=F2。
我们对按钮事件进行修改,修改为下面的代码,在看看执行顺序:
/// /// 按钮点击事件/// /// /// private async void btnStart_Click(object sender, EventArgs e){//string i1 = await F1Async(); //MessageBox.Show("i1=" + i1); //string i2 = await F2Async(); //MessageBox.Show("i2=" + i2); Task task1 = F1Async(); Task task2 = F2Async(); string i1 = await task1; MessageBox.Show("i1=" + i1); string i2 = await task2; MessageBox.Show("i2=" + i2); }
再次运行程序,查看输出顺序:
F1 Start->F2 Start->F1 Run->i1=F1->F2 Run->i2=F2。
可以看出两次的执行顺序不一致。这是什么原因呢?
这是因为并不是到了await才开始执行Task异步任务,执行到Task task1=F1Async()这句代码的时候,F1Async异步任务就开始执行了。同理,执行到下一句代码就开始执行F2Async异步任务了。await是为了保证执行到这里的时候异步任务一定执行完。执行到await的时候,如果异步任务还没有执行,那么就等待异步任务执行完。如果异步任务已经执行完了,那么就直接获取异步任务的返回值。
我们上面解释的原因是否正确呢?我们可以做下面的一个实验,来验证上面说的原因,我们把按钮事件里面的await注释掉,看看异步方法还会不会执行,代码如下:
/// /// 按钮点击事件/// /// /// private async void btnStart_Click(object sender, EventArgs e){//string i1 = await F1Async(); //MessageBox.Show("i1=" + i1); //string i2 = await F2Async(); //MessageBox.Show("i2=" + i2); Task task1 = F1Async(); Task task2 = F2Async(); // 并不是到了await才开始执行Task异步任务,这里是保证异步任务一定执行完// 代码执行到这里的时候,如果没有执行完就等待执行完,如果已经执行完,就直接获取返回值// 这里注释掉await代码段,查看异步方法是否会执行//string i1 = await task1; //MessageBox.Show("i1=" + i1); //string i2 = await task2; //MessageBox.Show("i2=" + i2); }
运行程序,发现异步方法还是会执行,这就说明我们上面解释的原因是正确的。感兴趣的可以使用Reflector反编译查看内部实现的原理,主要是MoveNext()方法内部。
我们可以得到下面的结论:
- 只要方法是Task
类型的返回值,都可以用await来等待调用获取返回值。 - 如果一个返回Task
类型的方法被标记了async,那么只要方法内部直接return T这个类型的实例就可以了。 - 一个返回Task
类型的方法如果没有被标记为async,那么需要方法内部直接return一个Task的实例。
/// /// 方法标记为async 直接返回一个int类型的数值即可/// ///private async Task F3Async(){return 2; }
上面的第三点可以看下面的代码:
/// /// 方法没有被标记为async,直接返回一个Task/// ///private Task F4Async(){return Task.Run (() => {return 2; }); }
二、TPL高级 我们做一些总结:
1、如果方法内部有await,则方法必须标记为async。await和async是成对出现的,只有await没有async程序会报错。只有async没有await,程序会按照同步方法执行。
2、ASP.NET MVC中的Action方法和WinForm中的事件处理方法都可以标记为async,控制台的Main()方法不能被标记为async。对于不能标记为async的方法怎么办呢?我们可以使用Result属性来获取值,看下面代码:
using System; using System.Net.Http; using System.Threading.Tasks; namespace AsyncDemo{class Program{static void Main(string[] args){// 实例化对象HttpClient client = new HttpClient(); // 调用异步的Get方法Task taskMsg = client.GetAsync("http://www.baidu.com"); // 通过Result属性获取返回值HttpResponseMessage msg = taskMsg.Result; Task taskRead = msg.Content.ReadAsStringAsync(); string html = taskRead.Result; Console.WriteLine(html); Console.ReadKey(); }}}
不建议使用这种方式,这样体现不出异步带来的好处,而且使用Result属性,有可能会带来上下文切换造成的死锁。下面我们来看看创建Task的方法。
1、如果返回值就是一个立即可以随手得到的值,那么就用Task.FromResult()。看下面代码:
static TaskTestAsync(){//return Task.Run (() => //{//return 5; //}); // 简便写法return Task.FromResult(3); }
2、如果是一个需要休息一会的任务(比如下载失败则过5秒钟后重试。主线程不休息,和Thread.Sleep不一样),那么就用Task.Delay()。
3、Task.Factory.FromAsync()会把IAsyncResult转换为Task,这样APM风格的API也可以用await来调用。
4、编写异步方法的简化写法。如果方法声明为async,那么可以直接return具体的值,不用在创建Task,由编译器创建Task,看下面的代码:
static async TaskTest2Async(){// 复杂写法//return await Task.Run (() => //{//return 5; //}); // 下面是简化写法,直接返回return 6; }
到此这篇关于C#多线程TPL模式高级用法探秘的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持脚本之家。
推荐阅读
- C#多线程TPL常见操作误区与异常处理
- OS|多进程和多线程的区别是什么(多进程和多线程的优缺点分析)
- JavaEE|多线程(一)线程和进程的区别
- 曝猛! 许多行情软件自带的SAR指标是错的
- k8s集群Job|k8s集群Job Pod 容器可能因为多种原因失效,想要更加稳定的使用Job负载,有哪些需要注意的地方()
- 从多快好省到好快省多,您的项目管理走对了吗()
- vue中keep-alive组件实现多级嵌套路由的缓存
- uni-app入门及练手项目
- 如何用less和sass生成padding、margin的四个方向多个值()
- docker|Kubernetes基础知识与k8s多节点案例配置