当我们提及多线程的时候会想到thread和threadpool,这都是异步操作,threadpool其实就是thread的集合,具有很多优势,不过在任务多的时候全局队列会存在竞争而消耗资源。thread默认为前台线程,主程序必须等线程跑完才会关闭,而threadpool相反。
总结:threadpool确实比thread性能优,但是两者都没有很好的api区控制,如果线程执行无响应就只能等待结束,从而诞生了task任务。
.NET 4.0推出了新一代的多线程模型Task,task简单地看就是任务,那和thread有什么区别呢?Task的背后的实现也是使用了线程池线程,但它的性能优于ThreadPoll,因为它使用的不是线程池的全局队列,而是使用的本地队列,使线程之间的资源竞争减少。同时Task提供了丰富的API来管理线程、控制。但是相对前面的两种耗内存,Task依赖于CPU,对于多核的CPU性能远超前两者,单核的CPU三者的性能没什么差别。
创建一个task任务有两种模式:使用factory创建会直接执行,使用new创建不会执行,必须等到start启动之后才执行。
class Program
{
static void DownLoad(object str)
{
Console.WriteLine("DownLoad Begin ID = " + Thread.CurrentThread.ManagedThreadId + " " + str);
Thread.Sleep(1000);
Console.WriteLine("DownLoad End");
}
static void Main(string[] args)
{
//创建任务
//Task task = new Task(DownLoad, "人民日报");
//启动任务
//task.Start();
//创建任务工厂
TaskFactory taskFactory = new TaskFactory();
//开始新的任务
taskFactory.StartNew(DownLoad, "纽约时报");
Console.WriteLine("Main");
Console.ReadKey();
}
}
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。
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就往下继续执行。
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);
首先创建一个取消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?
CancellationTokenSource tokenSource = new CancellationTokenSource();//创建取消task实例
Task testTask = new Task(() =>
{
for (int i = 0; i < 11; i++)
{
Console.WriteLine("fdsaf");
Thread.Sleep(1000);
}
}, tokenSource.Token);
Console.WriteLine(testTask.Status);
testTask.Start();
Console.WriteLine(testTask.Status);
tokenSource.Token.Register(() =>
{
Console.WriteLine("task is to cancel");
});
Thread.Sleep(6000);
tokenSource.Cancel();
Console.WriteLine(testTask.Status);
for (int i = 0; i < 20; i++)
{
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还在继续跑。
当我们调用了Cancel()方法之后,.NET Framework不会强制性的去关闭运行的Task。
我们自己必须去检测之前在创建Task时候传入的那个CancellationToken。
我们在创建Task是传入CancellationToken到构造函数,其实这个CancellationToken就是.NET Framework用来避免我们再次运行已经被取消的Task,可以说就是一个标志位。
对于线程的取消,查看这里。
在一个任务中可以启动子任务,两个任务异步执行。默认情况下,子任务(即由外部任务创建的内部任务)将独立于其父任务执行。使用TaskCreationOptions.AttachedToParent
显式指定将任务附加到任务层次结构中的某个父级。
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执行完。
如果父任务执行完了但是子任务没有执行完,则父任务的状态会被设置为WaitingForChildrenToComplete
,只有子任务也执行完了,父任务的状态才会变成RunToCompletion
。
使用Task的泛型版本,可以返回任务的执行结果。
下面例子中的TaskWithResult的输入为object类型,返回一个元组Tuple
。
定义调用TaskWithResult的任务时,使用泛型类Task
,泛型的参数定义了返回类型。通过构造函数,传递TaskWithResult
,构造函数的第二个参数定义了TaskWithResult
的输入值。
任务完成后,通过Result属性获取任务的结果。
class Program
{
static Tuple<int, int> TaskWithResult(object obj)
{
Tuple<int, int> div = (Tuple<int, int>)obj;
Thread.Sleep(1000);
return Tuple.Create<int, int>(div.Item1 + div.Item2, div.Item1 - div.Item2);
}
static void Main(string[] args)
{
var task = new Task<Tuple<int, int>>(TaskWithResult, Tuple.Create<int, int>(8, 3));
task.Start();
Console.WriteLine(task.Result);
task.Wait();
Console.WriteLine("Result: {0} {1}", task.Result.Item1, task.Result.Item2);
Console.ReadLine();
}
}
/*
执行结果
(11, 5)
result:11 5
*/
我们可以设置最大等待时间,如果超过了等待时间,就不再等待,下面我们来修改代码,设置最大等待时间为5秒(项目中可以根据实际情况设置),如果超过5秒就输出哪个任务出错了。
举例来说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
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace DemoAsync{
class Program{
static void Main(string[] args)
{
Console.WriteLine("Task With Thread Start !");
for (int i = 0; i <= 5; i++)
{
Thread t = new Thread(Dotaskfunction);
t.Start();
}
Console.WriteLine("Task With Thread End !");
Console.WriteLine("Task With Task Start !");
for (int i = 0; i <= 5; i++)
{
Task.Run(() => { Dotaskfunction(); });
}
Console.WriteLine("Task With Task End !");
Console.ReadLine();
}
public static void Dotaskfunction()
{
Console.WriteLine("task has been done! ThreadID: {0},IsBackGround:{1} ", Thread.CurrentThread.ManagedThreadId,Thread.CurrentThread .IsBackground );
}
}
}
可以看到Thread方法每次的Thread Id都是不同的,而Task方法的Thread Id是重复出现的。我们知道线程的创建和销毁是一个开销比较大的操作,Task每次执行将不会立即创建一个新线程,而是到CLR线程池查看是 否有空闲的线程,有的话就取一个线程处理这个请求,处理完请求后再把线程放回线程池,这个线程也不会立即撤销,而是设置为空闲状态,可供线程池再次调度, 从而减少开销。
要点:
1、当在主线程中创建了一个线程,那么该线程的IsBackground默认是设置为FALSE的。
2、当主线程退出的时候,IsBackground=FALSE的线程还会继续执行下去,直到线程执行结束。
3、只有IsBackground=TRUE的线程才会随着主线程的退出而退出。
4、当初始化一个线程,把Thread.IsBackground=true的时候,指示该线程为后台线程。后台线程将会随着主线程的退出而退出。
5、原理:只要所有前台线程都终止后,CLR就会对每一个活在的后台线程调用Abort()来彻底终止应用程序。
Net的公用语言运行时(Common Language Runtime,CLR)能区分两种不同类型的线程:前台线程和后台线程。这两者的区别就是:应用程序必须运行完所有的前台线程才可以退出;而对于后台线程,应用程序则可以不考虑其是否已经运行完毕而直接退出,所有的后台线程在应用程序退出时都会自动结束。
既然前台线程和后台线程有这种差别,那么我们怎么知道该如何设置一个线程的IsBackground属性呢?下面是一些基本的原则:对于一些在后台运行的线程,当程序结束时这些线程没有必要继续运行了,那么这些线程就应该设置为后台线程。比如一个程序启动了一个进行大量运算的线程,可是只要程序一旦结束,那个线程就失去了继续存在的意义,那么那个线程就该是作为后台线程的。而对于一些服务于用户界面的线程往往是要设置为前台线程的,因为即使程序的主线程结束了,其他的用户界面的线程很可能要继续存在来显示相关的信息,所以不能立即终止它们。这里我只是给出了一些原则,具体到实际的运用往往需要编程者的进一步仔细斟酌。
一般后台线程用于处理时间较短的任务,如在一个Web服务器中可以利用后台线程来处理客户端发过来的请求信息。而前台线程一般用于处理需要长时间等待的任务,如在Web服务器中的监听客户端请求的程序,或是定时对某些系统资源进行扫描的程序。
threadpool:
task:
本节内容来自这里。
async/await特性是与Task紧密相关的,所以在了解async/await
前必须充分了解Task的使用。
1、从 Main 方法执行到CountCharactersAsync(1, url1)
方法时,该方法会立即返回,然后才会调用它内部的方法开始下载内容。该方法返回的是一个Task
类型的占位符对象,表示计划进行的工作。这个占位符最终会返回 int 类型的值。
2、这样就可以不必等CountCharactersAsync(1, url1)
方法执行完成就可以继续进行下一步操作。到执行CountCharactersAsync(2, url2)
方法时,一样返回Task
对象。
3、然后,Main
方法继续执行三次ExtraOperation
方法,同时两次 CountCharactersAsync
方法依然在持续工作 。
4、t1.Result
和t2.Result
是指从CountCharactersAsync
方法调用的Task
对象取结果,如果还没有结果的话,将阻塞,直有结果返回为止。
async/await 结构可分成三部分:
(1)调用方法:该方法调用异步方法,然后在异步方法执行其任务的时候继续执行;
(2)异步方法:该方法异步执行工作,然后立刻返回到调用方法;
(3)await 表达式:用于异步方法内部,指出需要异步执行的任务。一个异步方法可以包含多个 await 表达式(不存在 await 表达式的话 IDE 会发出警告)。
异步方法:在执行完成前立即返回调用方法,在调用方法继续执行的过程中完成任务。
语法分析:
(1)关键字:方法头使用async
修饰。
(2)要求:包含 N(N>0) 个await
表达式(不存在await
表达式的话 IDE 会发出警告),表示需要异步执行的任务,没有的话,就和普通方法一样执行了。
(3)返回类型:只能返回 3 种类型(void
、Task
和Task\
)。Task
和Task\
标识返回的对象会在将来完成工作,表示调用方法和异步方法可以继续执行。
(4)参数:数量不限,但不能使用 out 和 ref 关键字。
(5)命名约定:方法后缀名应以 Async 结尾。
(6)其它:匿名方法和 Lambda 表达式也可以作为异步对象;async 是一个上下文关键字;关键字 async 必须在返回类型前。
关于 async 关键字:
1、在返回类型之前包含 async 关键字;
2、它只是标识该方法包含一个或多个 await 表达式,即,它本身不创建异步操作;
3、它是上下文关键字,即可作为变量名。
1、Task
internal class Calculator
{
private static int Add(int n, int m)
{
return n + m;
}
public static async Task<int> AddAsync(int n, int m)
{
int val = await Task.Run(() => Add(n, m));
return val;
}
}
private static void Main(string[] args)
{
Task<int> t = Calculator.AddAsync(1, 2);
//一直在干活
Console.WriteLine($"result: {t.Result}");
Console.Read();
}
2、Task:调用方法不需要从异步方法中取返回值,但是希望检查异步方法的状态,那么可以选择可以返回 Task 类型的对象。不过,就算异步方法中包含 return 语句,也不会返回任何东西。
internal class Calculator
{
private static int Add(int n, int m)
{
return n + m;
}
public static async Task AddAsync(int n, int m)
{
int val = await Task.Run(() => Add(n, m));
Console.WriteLine($"Result: {val}");
}
}
private static void Main(string[] args)
{
Task t = Calculator.AddAsync(1, 2);
//一直在干活
t.Wait();
Console.WriteLine("AddAsync 方法执行完成");
Console.Read();
}
(3)void:调用方法执行异步方法,但又不需要做进一步的交互。
internal class Calculator
{
private static int Add(int n, int m)
{
return n + m;
}
public static async void AddAsync(int n, int m)
{
int val = await Task.Run(() => Add(n, m));
Console.WriteLine($"Result: {val}");
}
}
private static void Main(string[] args)
{
Calculator.AddAsync(1, 2);
//一直在干活
Thread.Sleep(1000); //挂起1秒钟
Console.WriteLine("AddAsync 方法执行完成");
Console.Read();
}
c#之task与thread区别及其使用:https://blog.csdn.net/qq_40677590/article/details/102797838
c#中任务Task:https://blog.csdn.net/liyazhen2011/article/details/81262582
走进异步编程的世界 - 剖析异步方法(上):https://www.cnblogs.com/liqingwen/p/5844095.html
走进异步编程的世界 - 剖析异步方法(下):cnblogs.com/liqingwen/p/5866241.html