3、类 Task
参考资料:百度安全验证
面试必备:请问C#中Task和Thread有区别吗?如果有请简述区别_Run
task与thread的区别和使用讲解 https://www.jb51.net/article/250950.htm
3.1、Task的用途
task是异步编程的的核心,task在线程池的基础上进行了优化,提供了更多的API,Task类专门擅长于异步操作,于是就有了一个问题,thread也是异步编程,那么thread和Task有什么区别呢?为了回答这个问题应该梳理一下Task是如何来的。
3.2 、Task的来历
第一步,我们首先有了thread,这个类用以创造一些线程,用以实现异步编程
第二步,在现成的基础上,人们有发明了线程池 Threadpool, Threadpool其实就是thread的集合,具有很多优势,不过在任务多的时候全局队列会存在竞争而消耗资源。
线程池比之thread有许多优点:
thread默认为前台线程,主程序必须等线程跑完才会关闭,而threadpool相反。
但线程池也有如下缺点
- 不支持线程的取消,就像公园的摇摇车,坐上去后必须等这一圈跑完才能下来。
- 不支持线程执行的先后次序,这个有点像车站的出租车,坐上去后司机启动车的快慢乘客是无法控制的,因此当任务多的时候全局队列会存在竞争而消耗资源。
第三步,在上述线程池的基础上发展出了Task
task简单地看就是任务,那和thread有什么区别呢?
Task的背后的实现也是使用了线程池线程,但它的性能优于ThreadPoll,因为它使用的不是线程池的全局队列,而是使用的本地队列,使线程之间的资源竞争减少。
同时Task提供了丰富的API来管理线程、控制。
但是相对前面的两种耗内存,Task依赖于CPU对于多核的CPU性能远超前两者,单核的CPU三者的性能没什么差别。
于是我们可以回答3.1提出的问题,thread和Task有什么区别呢?
task是根据自己需要调用线程
thread就是个基本单位
简单地说,thread是单核多线程,task是多核多线程
Task是将多个操作封装成一个概念上原子操作。但这个操作由哪个Thread甚至多个Thread来处理处理你并不清楚。总之就是可以被正常完成。
Thread仅仅是一条线程,所有操作都是这个Thread一个人完成的。
Task较新,发布于.NET 4.5,能结合新的async/await代码模型写代码,它不止能创建新线程,还能使用线程池(默认)、单线程等方式编程,在UI编程领域,Task还能自动返回UI线程上下文,还提供了许多便利API以管理多个Task。
- Task默认使用线程池,也就是后台线程:当主线程结束时,你创建所有的tasks都会结束。
- Task.Run返回一个Task对象,可以使用它来监视其过程
- 在Task.Run之后,我们没有调用Start,因为该方法创建的是“热”任务(hot task)
- 可以通过task的构造函数创建“冷”任务(cold task),但开发中很少这么干
- 通过Task的Status属性来跟踪task的执行状态。
Task属于多核开发的封装。跟单线程工作的任务有很大的区别,甚至是本质上的区别。所以可比性不大。
3.3、Task的使用方法
Task类的使用与委托密不可分,就像delegate委托用泛型来实现时根据被委托的函数是否有返回值可以分为Action<...>和Func<...,Tresult>两种,Task类在使用时叶根据对应的委托是否有返回值分为两种:
Task类 | 对应void方法 | Task(Action) |
Task<TResult> | 有返回值的方法 | Task<TRsult>(Func<TResult>) |
Task taskA = Task.Run( () => Thread.Sleep(2000));
(1)无参数Task类
这种一共有以下几种使用方法
using System;
using System.Threading;
using System.Threading.Tasks;
class Example
{
static void Main()
{
Action<object> action = (object obj) =>
{
Console.WriteLine("Task={0}, obj={1}, Thread={2}",
Task.CurrentId, obj,
Thread.CurrentThread.ManagedThreadId);
};
// t1这个任务是用声明一个Task类的实例的方法instantiated。
Task t1 = new Task(action, "alpha");
// t2这个任务实现用了Task.Factory.StartNew(action, "beta");这个语句。
Task t2 = Task.Factory.StartNew(action, "beta");
// Block the main thread to demonstrate that t2 is executing
t2.Wait();//这里代表着要等t2结束再往下运行
// 运行 t1
t1.Start();
Console.WriteLine("t1 has been launched. (Main Thread={0})",
Thread.CurrentThread.ManagedThreadId);
// Wait for the t1 task to finish.
t1.Wait();
// Construct a started task using Task.Run.用Task.Run这个语句构建了一个已经开始的任务t3;
String taskData = "delta";
Task t3 = Task.Run( () => {Console.WriteLine("Task={0}, obj={1}, Thread={2}",
Task.CurrentId, taskData,
Thread.CurrentThread.ManagedThreadId);
});
// Wait for the task t3 to finish.
t3.Wait();
// Construct an unstarted task
Task t4 = new Task(action, "gamma");
// Run it synchronously
t4.RunSynchronously();
// Although the task was run synchronously, it is a good practice
// to wait for it in the event exceptions were thrown by the task.
t4.Wait();
}
}
// The example displays output like the following:
// Task=1, obj=beta, Thread=3
// t1 has been launched. (Main Thread=1)
// Task=2, obj=alpha, Thread=4
// Task=3, obj=delta, Thread=3
// Task=4, obj=gamma, Thread=1
由上面的一段程序可以知道,使用无参数的task非常简单:
Task t1 ;
public static void metha()
{
Console.WriteLine("zzzzzzzzzzz");
}Action A = metha; //无参无返
//===================================================
方法1 : t1= Task.Run(A);
方法2: t1 = Task.Run(() => { Console.WriteLine("hello"); });
方法3 : t1 = Task.Run(() => metha());
方法4 : t1 = new Task(A);
(2)有参数的Task
这里主要说的是在运行的方法有参数时如何使用Task.Run()方法打开一个任务
t这个方法比较多的用到了lamda表达式,所以先看一下lamda表达式的用法
lamda表达式实际上是一种匿名函数,=>这个符号叫做lamda运算符,
var t2= Task.Run( () =>
{
Console.WriteLine("task start");
});
观察下,run后边括号里的部分变成了
() => { Console.WriteLine("task start"); }
lamdba后边的中括号前是不是少了函数名?这就是匿名函数。
来看一下,lamdba表达式后边是函数名,注意到差别了吗?
var A = Task.Run(() => R());
//========================================================//
static void R()
{
。。。
}
于是联系以下方法的声明,前边括号里表示的应该时形参,于是可以知道,空括号表示0个输入函数。下面一段代码实现了一些lamda表达式情况下task.Run()的用法。
static void HaveParam(string a)
{
Console.WriteLine(a);
}
Task t2 = Task.Run(() => HaveParam("hello,word"));//这里就不能用提前做的委托了
Action<string > bcc = (string obj) =>
{
Console.WriteLine(obj);
};
Task t4 = new Task((Action<object>)bcc, "have");
Action<string> action_Have = HaveParam;//有参无返
t4 = new Task((Action<object>)action_Have, "have");
Action<object> acc = (object o) =>
{
Console.WriteLine(o);
};
t4 = new Task(acc, "a");
t4 = new Task(acc, 1);
这种情况下限制就有点多了,能用的只有方法3和方法4了,这中间比较常用的是方法3,方法4应该注意用的时候形参的数据格式用object,用其他的还得转一下数据格式
方法3 : t1 = Task.Run(() => metha());
方法4 : t1 = new Task(A);
以上是指只有单个的参数的情况,看task构造函数的定义会发现,方法4只能适用于只有一个参数的情况,所以当需要传递的参数大于1时,就只能用方法3,这就是说对于task.run()来说,括号里边的委托要么在括号里完成,要么就在外边把参数传递好,总之,不要向task里边放一个还没传递好参数的且已经是成品的委托,run()的括号里边是不适合向委托传递函数的。
方法3 : t1 = Task.Run(() => metha());
方法3就是,向函数metha()里边传递函数,传递完了再通过lamdba表达式转变为委托,于是括号里边是一个传递好函数的委托,
如果非要用方法4的话,A应该是一个传递好参数的委托,
将函数比作一个礼物,必须在完全做好后才能放进礼物盒(委托)中,然后再将礼物送给快递员(Task.Run())。就像下边,方法prinf在通过lamdba包装好放进aaa前,就完成了参数传递。给task.run()的是一个完整的盒子。
Action aaa =()=> prinf("a");
Task tttt = Task.Run(aaa);
Task ss = Task.Run(() => prinf ("aaa"));
Task s = Task.Run(() => Console.WriteLine("hello"));
static void prinf(string a)
{
Console.WriteLine(a);
}
这个来源于task这个api的编写,除非重写api,否则也就这个功能了。
到这里无返回值的task几乎完毕了,剩下的就是t.wait();t.result等等,这些就自己看一下微软的定义就好。
Task Class (System.Threading.Tasks) | Microsoft Docs
(3)有返回值的task
由上可知,我可以不再理会方法4,只用方法3的话这里非常简单
var t = Task<int>.Run(() => {
int max = 1000000;
return max;
});
Console.WriteLine("Finished {0:N0} iterations.", t.Result);
var Rstring = Task.Run(() => R_P("string"));
Rstring = Task.Run(() => R());
//========================================================//
static string R_P(string a)
{
return a;
}
static string R()
{
string a = "hello";
return a;
}
这里需要注意的是数据类型,c#语言是一个强类型函数在这里表现得非常明显,当使用var来声明一个变量t,这个t就可以有result,也就是返回值,但如果用task来声明t那么无法取得返回值
右边的确返回了一个Task类型的值,但在这里最好使用var,var可以用task的功能,比如
var t,t可以这样:t.wait();
也可以t.result;
当然如果使用task的话,可以在前面声明一个Task a,然后a可以放在任何一个等式的左边
如果使用一个var的话,就像上面的程序
var t中的t接了一个int型的值,于是t就成了int型的变量,t不能代替Rstring,但Rstring可以放在第2.3个等式前边,因为在第二个等式中Rstring成了string型的变量,第三个等式的返回值也是string,于是可以使用。
其余的东西大同小异可以自己看说明书。