.NET Task,async,await的详解
Task
同步和异步
说Task之前,先说一个基本概念,异步,正常的程序在执行时会按照调用的先后顺序执行,当一个靠前的方法还没有执行完毕,就不会执行后面的代码,而异步就是让一个方法的执行过程独立出去,当执行一个异步方法或一段异步代码时,这个方法或代码不会占用主线程,而是新创建一个线程与主线程同时执行。这样就可以让那些很耗资源的加载代码异步执行,让主线程空出来做别的事,比如显示加载进度、绘制界面之类的。Task
在Task之前多线程是用ThreadPool实现的,虽然确实能实现多线程,但不能控制线程的执行顺序,也不能获取线程内程序的执行状况(异常或成功),所以.NET4.0在ThreadPool的基础上推出了Task,保留了ThreadPool优点的同时,也解决了ThreadPool的问题。Task线程的创建主要有三种方式:
//1.new方式实例化一个Task,需要通过Start方法启动
Task task = new Task(() =>
{
Thread.Sleep(100);
Console.WriteLine($"hello, task1的线程ID为{Thread.CurrentThread.ManagedThreadId}");
});
task.Start();
//2.Task.Factory.StartNew(Action action)创建和启动一个Task
Task task2 = Task.Factory.StartNew(() =>
{
Thread.Sleep(100);
Console.WriteLine($"hello, task2的线程ID为{ Thread.CurrentThread.ManagedThreadId}");
});
//3.Task.Run(Action action)将任务放在线程池队列,返回并启动一个Task
Task task3 = Task.Run(() =>
{
Thread.Sleep(100);
Console.WriteLine($"hello, task3的线程ID为{ Thread.CurrentThread.ManagedThreadId}");
});
Console.WriteLine("执行主线程!");
执行结果:
执行主线程!
hello, task2的线程ID为6
hello, task1的线程ID为5
hello, task3的线程ID为4
上面展示的是没有返回值的Task,可以看到Task并没有影响到主进程的执行,程序先执行完了主线程,再执行异步的线程。我们也可以创建有返回值的Task:
static void Main(string[] args)
{
Task<string> tt1 = t1();
tt1.Start();
Task<string> tt2 = t1();
tt2.Start();
Task<string> tt3 = t1();
Task<string> tt4 = t1();
tt4.Start();
Console.WriteLine("tt1:"+tt1.Result);
Console.WriteLine("执行主线程!");
Console.WriteLine("tt2:"+tt2.Result);
Console.WriteLine("tt3:"+tt3.Result);
Console.WriteLine("tt4:"+tt4.Result);
Console.ReadKey();
}
public static Task<string> t1()
{
Thread.Sleep(1000);
return new Task<string>(()=> { return "线程ID:" + Thread.CurrentThread.ManagedThreadId; });
}
执行结果:
tt1:线程ID:4
执行主线程!
tt2:线程ID:5
Task.Result可以获得Task委托的执行结果,但会阻塞主线程,因为tt3没有start所以不会返回值,但主线程一直在等待tt3的执行结果,导致了tt4的结果一致没能输出,但实际上tt4已经执行结束了。
Async/await
Async是方法的修饰符,修饰的方法返回值必须为Task、Task<>、void。必须和await一起使用才有意义,而await用在Task委托的前面,效果先看代码:
static void Main(string[] args)
{
Task<string> tt1 = t1();
tt1.Start();
tt1.Wait();
Console.WriteLine("执行主线程");
Console.WriteLine(t2().Result);
Console.ReadKey();
}
public static Task<string> t1()
{
Thread.Sleep(1000);
Console.WriteLine("t1:线程ID:" + Thread.CurrentThread.ManagedThreadId);
return new Task<string>(() => { return "t1:线程ID:" + Thread.CurrentThread.ManagedThreadId; });
}
public static async Task<string> t2()
{
Task<string> tt2 = Getstr();
await tt2;
string str = tt2.Result;
return str;
}
public static Task<string> Getstr()
{
Thread.Sleep(1000);
return Task<string>.Run(() => { return "Getstr"; });
}
输出结果:
t1:线程ID:1
执行主线程
Getstr
Task.wait的效果是让主线程阻塞,等待Task执行完成再继续执行,而await效果相同,不过阻塞的是t2方法,也就是await修饰的方法,到这里可能会有人想,如果让主线程等待其他线程,那不就和同步一样了吗,看上去确实差不多,但await并不会造成线程的阻塞(web程序感觉不到)。
最后需要说的是,线程并不能提升程序的执行速度,只能提升程序的执行效率(吞吐量)。同步异步各有优劣,需要分情况选择。