c#之task与thread区别及其使⽤
1.什么是thread
当我们提及多线程的时候会想到thread和threadpool,这都是异步操作,threadpool其实就是thread的集合,具有很多优势,不过在任务多的时候全局队列会存在竞争⽽消耗资源。thread默认为前台线程,主程序必须等线程跑完才会关闭,⽽threadpool相反。
总结:threadpool确实⽐thread性能优,但是两者都没有很好的api区控制,如果线程执⾏⽆响应就只能等待结束,从⽽诞⽣了task任务。
2.什么是task
task简单地看就是任务,那和thread有什么区别呢?Task的背后的实现也是使⽤了线程池线程,但它的性能优于ThreadPoll,因为它使⽤的不是线程池的全局队列,⽽是使⽤的本地队列,使线程之间的资源竞争减少。同时Task提供了丰富的API来管理线程、控制。但是相对前⾯的两种耗内存,Task依赖于CPU对于多核的CPU性能远超前两者,单核的CPU三者的性能没什么差别。
3.创建⼀个task任务有两种模式:
1.使⽤factory创建会直接执⾏,使⽤new创建不会执⾏,必须等到start启动之后才执⾏。
public void test()
{
var testTask = new Task(() =>
{
Console.WriteLine("task start");
});
testTask.Start();
var factoryTeak = Task.Factory.StartNew(() =>
{
writeline用什么替代Console.WriteLine("factory task start");
});
}
2.我们来看看task的⽣命周期
var testTask = new Task(() =>
{
Console.WriteLine("task start");
System.Threading.Thread.Sleep(2000);
});
Console.WriteLine(testTask.Status);
testTask.Start();
Console.WriteLine(testTask.Status);
Console.WriteLine(testTask.Status);
testTask.Wait();
Console.WriteLine(testTask.Status);
Console.WriteLine(testTask.Status);
输出结果:
Created
task start
Running
Running
RanToCompletion
RanToCompletion
可以看出task确实是异步执⾏,并且wait很好地控制了task。
3.下⾯演⽰⼏个控制task的⽅法
var testTask = new Task(() =>
{
Console.WriteLine("task start");
System.Threading.Thread.Sleep(2000);
});
testTask.Start();
testTask.Wait();
var testTask = new Task(() =>
{
Console.WriteLine("task start");
System.Threading.Thread.Sleep(2000);
});
testTask.Start();
var factoryTeak = Task.Factory.StartNew(() =>
{
Console.WriteLine("factory task start");
});
Task.WaitAll(testTask, factoryTeak);
Console.WriteLine("end");
var testTask = new Task(() =>
{
Console.WriteLine("task start");
System.Threading.Thread.Sleep(2000);
});
testTask.Start();
var factoryTeak = Task.Factory.StartNew(() =>
{
Console.WriteLine("factory task start");
});
Task.WaitAny(testTask, factoryTeak);
Console.WriteLine("end");
通过wait()对单个task进⾏等待,Task.waitall()对多个task进⾏等待,waitany()执⾏任意⼀个task就往下继续执⾏。
4.task的回调执⾏
var testTask = new Task(() =>
{
Console.WriteLine("task start");
System.Threading.Thread.Sleep(2000);
});
testTask.Start();
var resultTest = testTask.ContinueWith<string>((Task) => {
Console.WriteLine("testTask end");
return "end";
});
Console.WriteLine(resultTest.Result);
5.task的取消
⾸先创建⼀个取消task的令牌的实例,在不启动task直接取消:
var tokenSource = new CancellationTokenSource();//创建取消task实例            var testTask = new Task(() =>
{
for (int i = 0; i < 6; i++) {
System.Threading.Thread.Sleep(1000);
}
},tokenSource.Token);
Console.WriteLine(testTask.Status);
tokenSource.Token.Register(()=> {
Console.WriteLine("task is to cancel");
});
tokenSource.Cancel();
Console.WriteLine(testTask.Status);
输出结果:
Created
task is to cancel
Canceled
如果task启动了真的取消了task?
var tokenSource = new CancellationTokenSource();//创建取消task实例            var testTask = new Task(() =>
{
for (int i = 0; i <6; i++) {
System.Threading.Thread.Sleep(1000);
}
},tokenSource.Token);
Console.WriteLine(testTask.Status);
testTask.Start();
Console.WriteLine(testTask.Status);
tokenSource.Token.Register(()=> {
Console.WriteLine("task is to cancel");
});
tokenSource.Cancel();
Console.WriteLine(testTask.Status);
for (int i = 0; i < 10; i++)
{
System.Threading.Thread.Sleep(1000);
Console.WriteLine(testTask.Status);
}
输出结果:
Created
WaitingToRun
task is to cancel
Running
Running
Running
Running
Running
Running
RanToCompletion
RanToCompletion
RanToCompletion
RanToCompletion
RanToCompletion
可以看出其实并没有取消task,此时task还在继续跑。
6.task的嵌套
var parentTask = new Task(()=> {
var childTask = new Task(() =>{
System.Threading.Thread.Sleep(2000);
Console.WriteLine("childTask to start");
});
childTask.Start();
Console.WriteLine("parentTask to start");
});
parentTask.Start();
parentTask.Wait();
Console.WriteLine("end");
此时为普通关联,⽗task和⼦task没影响
var parentTask = new Task(()=> {
var childTask = new Task(() =>{
System.Threading.Thread.Sleep(2000);
Console.WriteLine("childTask to start");
}, TaskCreationOptions.AttachedToParent);
childTask.Start();
Console.WriteLine("parentTask to start");
} );
parentTask.Start();
parentTask.Wait();
Console.WriteLine("end");
此时为⽗task和⼦task关联,wait会⼀直等待⽗⼦task执⾏完。
6.task死锁的问题
我们可以设置最⼤等待时间,如果超过了等待时间,就不再等待,下⾯我们来修改代码,设置最⼤等待时间为5秒(项⽬中可以根据实际情况设置),如果超过5秒就输出哪个任务出错了
7.对Spinlock的使⽤
举例来说Parallel.for和Parallel.foreach是线程不安全的,有可能达不到你的预期,此时就需要加锁来解决此问题,我们可以加lock和spinlock(⾃旋锁)来解决
SpinLock slock = new SpinLock(false);
var testLock= new object();
long sum1 = 0;
long sum2 = 0;
long sum3 = 0;
Parallel.For(0, 100000, i =>
{
sum1 += i;
});
Parallel.For(0, 100000, i =>
{
bool lockTaken = false;
try
{
slock.Enter(ref lockTaken);
sum2 += i;
}
finally
{
if (lockTaken)
slock.Exit(false);
}
});
Parallel.For(0, 100000, i =>
{
lock(testLock)
{
sum3 += i;
};
});
Console.WriteLine("Num1的值为:{0}", sum1);            Console.WriteLine("Num2的值为:{0}", sum2);            Console.WriteLine("Num3的值为:{0}", sum3);
输出结果:
Num1的值为:1660913202
Num2的值为:4999950000
Num3的值为:4999950000
Num1的值为:2754493646
Num2的值为:4999950000
Num3的值为:4999950000
Num1的值为:4999950000
Num2的值为:4999950000
Num3的值为:4999950000
最后看看threadpoll和task的结构图:threadpool: