.NET中async异步、thread多线程
一、任务Task
System.Threading.Tasks在.NET4引入,前面线程的API太多了,控制不方便,而ThreadPool控制能力又太弱,比如做线程的延续、阻塞、取消、超时等功能不太方便,所以Task就抽象了线程功能,在后台使用ThreadPool
1、启动任务
可以使用TaskFactory类或Task类的构造函数和Start()方法,委托可以提供带有一个Object类型的输入参数,所以可以给任务传递任意数据,还漏了一个常用的Task.Run
TaskFactory taskFactory = new TaskFactory();
taskFactory.StartNew(() =>
{
Console.WrITeLine($"tid={
Thread.currentThread.ManagedThreadId}
,datetime={
DateTime.Now}
");
}
);
Task.Factory.StartNew(() =>
{
Console.WriteLine($"tid={
Thread.CurrentThread.ManagedThreadId}
,datetime={
DateTime.Now}
");
}
);
Task task = new Task(() =>
{
Console.WriteLine($"tid={
Thread.CurrentThread.ManagedThreadId}
,datetime={
DateTime.Now}
");
}
);
task.Start();
只有Task类实例方式需要Start()去启动任务,当然可以RunSynchronously()来同步执行任务,主线程会等待,就是用主线程来执行这个task任务
Task task = new Task(() =>
{
Thread.Sleep(10000);
Console.WriteLine($"tid={
Thread.CurrentThread.ManagedThreadId}
,datetime={
DateTime.Now}
");
}
);
task.RunSynchronously();
2、阻塞延续
在Thread中我们使用join来阻塞等待,在多个Thread时进行控制就不太方便。Task中我们使用实例方法Wait阻塞单个任务或静态方法WaitAll和WaitAny阻塞多个任务
VAR task = new Task(() =>
{
Thread.Sleep(5*1000);
Console.WriteLine($"tid={
Thread.CurrentThread.ManagedThreadId}
,datetime={
DateTime.Now}
");
}
);
var task2 = new Task(() =>
{
Thread.Sleep(10 * 1000);
Console.WriteLine($"tid={
Thread.CurrentThread.ManagedThreadId}
,datetime={
DateTime.Now}
");
}
);
task.Start();
task2.Start();
//task.Wait();
//单任务等待//Task.WaitAny(task, task2);
//任何一个任务完成就继续Task.WaitAll(task, task2);
//任务都完成才继续如果不希望阻塞主线程,实现当一个任务或几个任务完成后执行别的任务,可以使用Task静态方法WhenAll和WhenAny,他们将返回一个Task,但这个Task不允许你控制,将会在满足WhenAll和WhenAny里任务完成时自动完成,然后调用Task的ContinueWith方法,就可以在一个任务完成后紧跟开始另一个任务
Task.WhenAll(task, task2).ContinueWith((t) =>
{
Console.WriteLine($"tid={
Thread.CurrentThread.ManagedThreadId}
,datetime={
DateTime.Now}
");
}
);
Task.Factory工厂中也存在类似ContinueWhenAll和ContinueWhenAny3、任务层次结构
不仅可以在一个任务结束后执行另一个任务,也可以在一个任务内启动一个任务,这就启动了一个父子层次结构
var parentTask = new Task(()=>
{
Console.WriteLine($"parentId={
Thread.CurrentThread.ManagedThreadId}
,datetime={
DateTime.Now}
");
Thread.Sleep(5*1000);
var childTask = new Task(() =>
{
Thread.Sleep(10 * 1000);
Console.WriteLine($"childId={
Thread.CurrentThread.ManagedThreadId}
,datetime={
DateTime.Now}
") }
);
childTask.Start();
}
);
parentTask.Start();
如果父任务在子任务之前结束,父任务的状态为WaitingForChildrenTocomplete,当子任务也完成时,父任务的状态就变为RanToCompletion,当然,在创建任务时指定TaskCreationOptions枚举参数,可以控制任务的创建和执行的可选行为
4、枚举参数
简单介绍下创建任务中的TaskCreationOptions枚举参数,创建任务时我们可以提供TaskCreationOptions枚举参数,用于控制任务的创建和执行的可选行为的标志
AttachedToParent:指定将任务附加到任务层次结构中的某个父级,意思就是建立父子关系,父任务必须等待子任务完成才可以继续执行。和WaitAll效果一样。上面例子如果在创建子任务时指定TaskCreationOptions.AttachedToParent,那么父任务wait时也会等子任务的结束
DenyChildAttach:不让子任务附加到父任务上
LongRunning:指定是长时间运行任务,如果事先知道这个任务会耗时比较长,建议设置此项。这样,Task调度器会创建Thread线程,而不使用ThreadPool线程。因为你长时间占用ThreadPool线程不还,那它可能必要时会在线程池中开启新的线程,造成调度压力
PReferFairness:尽可能公平的安排任务,这意味着较早安排的任务将更可能较早运行,而较晚安排运行的任务将更可能较晚运行。实际通过把任务放到线程池的全局队列中,让工作线程去争抢,默认是在本地队列中。
另一个枚举参数是ContinueWith方法中的TaskContinuationOptions枚举参数,它除了拥有几个和上面同样功能的枚举值外,还拥有控制任务的取消延续等功能
LazyCancellation:在延续取消的情况下,防止延续的完成直到完成先前的任务。什么意思呢?
CancellationTokenSource source = new CancellationTokenSource();
source.Cancel();
var task1 = new Task(() =>
{
Console.WriteLine($"task1 id={
Thread.CurrentThread.ManagedThreadId}
,datetime={
DateTime.Now}
");
}
);
var task2 = task1.ContinueWith(t =>
{
Console.WriteLine($"task2 id={
Thread.CurrentThread.ManagedThreadId}
,datetime={
DateTime.Now}
");
}
,source.Token);
var task3 = task2.ContinueWith(t =>
{
Console.WriteLine($"task3 id={
Thread.CurrentThread.ManagedThreadId}
,datetime={
DateTime.Now}
");
}
);
task1.Start();
上面例子我们企图task1-> task2-> task3顺序执行,然后通过CancellationToken来取消task2的执行。结果会是怎样呢?结果task1和task3会并行执行(task3也是会执行的,而且是和task1并行,等于原来的一条链变成了两条链),然后我们尝试使用
LazyCancellation,var task2 = task1.ContinueWith(t =>
{
Console.WriteLine($"task2 id={
Thread.CurrentThread.ManagedThreadId}
,datetime={
DateTime.Now}
");
}
,source.Token,TaskContinuationOptions.LazyCancellation,TaskScheduler.Current);
这样,将会在task1执行完成后,task2才去判断source.Token,为Cancel就不执行,接下来执行task3就保证了原来的顺序
ExecuteSynchronously:指定应同步执行延续任务,比如上例中,在延续任务task2中指定此参数,则task2会使用执行task1的线程来执行,这样防止线程切换,可以做些共有资源的访问。不指定的话就随机,但也能也用到task1的线程
NotOnRanToCompletion:延续任务必须在前面任务非完成状态下执行
OnlyOnRanToCompletion:延续任务必须在前面任务完成状态才能执行
NotOnFaulted,OnlyOnCanceled,OnlyOnFaulted等等
5、任务取消
在上篇使用Thread时,我们使用一个变量isStop标记是否取消任务,这种访问共享变量的方式难免会出问题。task中提出CancellationTokenSource类专门处理任务取消,常见用法看下面代码注释
CancellationTokenSource source = new CancellationTokenSource();
//构造函数中也可指定延迟取消//注册一个取消时调用的委托source.Token.Register(() =>
{
Console.WriteLine("当前source已经取消,可以在这里做一些其他事情(比如资源清理)...");
}
);
var task1 = new Task(() =>
{
while (!source.IsCancellationRequested) {
Console.WriteLine($"task1 id={
Thread.CurrentThread.ManagedThreadId}
,datetime={
DateTime.Now}
");
}
}
,source.Token);
task1.Start();
//source.Cancel();
//取消source.CancelAfter(1000);
//延时取消6、任务结果
让子线程返回结果,可以将信息写入到线程安全的共享变量中去,或则使用可以返回结果的任务。使用Task的泛型版本TaskTResult> ,就可以定义返回结果的任务。Task是继承自Task的,Result获取结果时是要阻塞等待直到任务完成返回结果的,内部判断没有完成则wait。通过Taskstatus属性可获得此任务的状态是启动、运行、异常还是取消等
var task = new Taskstring>
(() =>
{
return "hello ketty";
}
);
task.Start();
string result = task.Result;
7、异常
可以使用AggregateException来接受任务中的异常信息,这是一个聚合异常继承自Exception,可以遍历获取包含的所有异常,以及进行异常处理,决定是否继续往上抛异常等
var task = Task.Factory.StartNew(() =>
{
var childTask1 = Task.Factory.StartNew(() =>
{
throw new Exception("childTask1异常...");
}
,TaskCreationOptions.AttachedToParent);
var childTask12= Task.Factory.StartNew(() =>
{
throw new Exception("childTask2异常...");
}
, TaskCreationOptions.AttachedToParent);
}
);
try{
try {
task.Wait();
}
catch (AggregateException ex) {
foreach (var item in ex.InnerExceptions) {
Console.WriteLine($"message{
item.InnerException.Message}
");
}
ex.Handle(x =>
{
if (x.InnerException.Message == "childTask1异常...") {
return true;
//异常被处理,不继续往上抛了 }
return false;
}
);
}
}
catch (Exception ex){
throw;
}
二、并行Parallel
1、Parallel.For()、Parallel.ForEach()
在.NET4中,另一个新增的抽象的线程时Parallel类。这个类定义了并行的for和foreach的静态方法。Parallel.For()和Parallel.ForEach()方法多次调用一个方法,而Parallel.Invoke()方法允许同时调用不同的方法。首先Parallel是会阻塞主线程的,它将让主线程也参与到任务中
Parallel.For()类似于for允许语句,并行迭代同一个方法,迭代顺序没有保证的
ParallelLoopResult result = Parallel.For(0, 10, i =>
{
Console.WriteLine($"{
i}
task:{
Task.CurrentId}
thread:{
Thread.CurrentThread.ManagedThreadId}
");
}
);
Console.WriteLine(result.IsCompleted);
也可以提前中断Parallel.For()方法。For()方法的一个重载版本接受Actionint,parallelloopstate style="font-Size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px; "> 类型参数。一般不使用,像下面这样,本想大于5就停止,但实际也可能有大于5的任务已经在跑了。可以通过ParallelOptions传入允许最大线程数以及取消Token等
ParallelLoopResult result = Parallel.For(0, 10, new ParallelOptions() {
MaxdegreeOfParallelism = 8 }
,(i,loop) =>
{
Console.WriteLine($"{
i}
task:{
Task.CurrentId}
thread:{
Thread.CurrentThread.ManagedThreadId}
");
if (i >
5) {
loop.break();
}
}
);
2、Parallel.ForTLocal>
For还有一个高级泛型版本,相当于并行的聚合计算
ParallelLoopResult ForTLocal>
(int FromInclusive, int toExclusive, FuncTLocal>
localInit, Funcint, ParallelLoopState, TLocal, TLocal>
body, ActionTLocal>
localFinally);
像下面这样我们求0…100的和,第三个参数更定一个种子初始值,第四个参数迭代累计,最后聚合
int totalNum = 0;
Parallel.Forint>
(0, 100, () =>
{
return 0;
}
, (current, loop, total) =>
{
total += current;
return total;
}
, (total) =>
{
Interlocked.Add(ref totalNum, total);
}
);
上面For用来处理数组数据,ForEach()方法用来处理非数组的数据任务,比如字典数据继承自IEnumerable的集合等
3、Parallel.Invoke()
Parallel.Invoke()则可以并行调用不同的方法,参数传递一个Action的委托数组
Parallel.Invoke(() =>
{
Console.WriteLine($"方法1 thread:{
Thread.CurrentThread.ManagedThreadId}
");
}
, () =>
{
Console.WriteLine($"方法2 thread:{
Thread.CurrentThread.ManagedThreadId}
");
}
, () =>
{
Console.WriteLine($"方法3 thread:{
Thread.CurrentThread.ManagedThreadId}
");
}
);
4、PLinq
Plinq,为了能够达到最大的灵活度,linq有了并行版本。使用也很简单,只需要将原始集合AsParallel就转换为支持并行化的查询。也可以AsOrdered来顺序执行,取消Token,强制并行等
var nums = Enumerable.Range(0, 100);
var query = from n in nums.AsParallel() select new {
thread=$"tid={
Thread.CurrentThread.ManagedThreadId}
,datetime={
DateTime.Now}
" }
;
三、异步等待AsyncAwait
异步编程模型,可能还需要大篇幅来学习,这里先介绍下基本用法,内在本质需要用ILSpy反编译来看,以后可能要分专题总结。文末先给几个参考资料,有兴趣自己阔以先琢磨琢磨鸭
1、简单使用
这是.NET4.5开始提供的一对语法糖,使得可以较简便的使用异步编程。async用在方法定义前面,await只能写在带有async标记的方法中,任何方法都可以增加async,一般成对出现,只有async没有意义,只有await会报错,请先看下面的示例
private static async void Asynctest(){
//主线程执行 Console.WriteLine($"before await ThreadId={
Thread.CurrentThread.ManagedThreadId}
");
TaskFactory taskFactory = new TaskFactory();
Task task = taskFactory.StartNew(() =>
{
Thread.Sleep(3000);
Console.WriteLine($"task ThreadId={
Thread.CurrentThread.ManagedThreadId}
");
}
);
await task;
//主线程到这里就返回了,执行主线程任务 //子线程执行,其实是封装成委托,在task之后成为回调(编译器功能 状态机实现) 后面相当于task.ContinueWith() //这个回调的线程是不确定的:可能是主线程 可能是子线程 也可能是其他线程,在winform中是主线程 Console.WriteLine($"after await ThreadId={
Thread.CurrentThread.ManagedThreadId}
");
}
一般使用async都会让方法返回一个Task的,像下面这样复杂一点的
private static async Taskstring>
Asynctest2(){
Console.WriteLine($"before await ThreadId={
Thread.CurrentThread.ManagedThreadId}
");
TaskFactory taskFactory = new TaskFactory();
string x = await taskFactory.StartNew(() =>
{
Thread.Sleep(3000);
Console.WriteLine($"task ThreadId={
Thread.CurrentThread.ManagedThreadId}
");
return "task over";
}
);
Console.WriteLine($"after await ThreadId={
Thread.CurrentThread.ManagedThreadId}
");
return x;
}
通过var reslult = AsyncTest2().Result; 调用即可。但注意如果调用Wait或Result的代码位于UI线程,Task的实际执行在其他线程,其需要返回UI线程则会造成死锁,所以应该Async all the way
2、优雅
从上面简单示例中可以看出异步编程的执行逻辑:主线程A逻辑-> 异步任务线程B逻辑-> 主线程C逻辑。
异步方法的返回类型只能是void、Task、Task。示例中异步方法的返回值类型是Task,通常void也不推荐使用,没有返回值直接用Task就是
上一篇也大概了解到如果我们要在任务中更新UI,需要调用Invoke通知UI线程来更新,代码看起来像下面这样,在一个任务后去更新UI
private void button1_Click(object sender, EventArgs e){
var ResultTask = Task.Run(() =>
{
Thread.Sleep(5000);
return "任务完成";
}
);
ResultTask.ContinueWith((r)=>
{
textBox1.Invoke(() =>
{
textBox1.Text = r.Result;
}
);
}
);
}
如果使用async/await会看起来像这样,是不是优雅了许多。以看似同步编程的方式实现异步
private async void button1_Click(object sender, EventArgs e){
var t = Task.Run(() =>
{
Thread.Sleep(5000);
return "任务完成";
}
);
textBox1.Text = await t;
}
3、最后
在.NET 4.5中引入的Async和Await两个新的关键字后,用户能以一种简洁直观的方式实现异步编程。甚至都不需要改变代码的逻辑结构,就能将原来的同步函数改造为异步函数。
在内部实现上,Async和Await这两个关键字由编译器转换为状态机,通过System.Threading.Tasks中的并行类实现代码的异步执行。
本文来自 C#.Net教程 栏目,欢迎学习!
以上就是.NET中async异步、thread多线程的详细内容,更多请关注其它相关文章!
声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!
若转载请注明出处: .NET中async异步、thread多线程
本文地址: https://pptw.com/jishu/593715.html
