C#(99):二、并行编程-Task任务

更新时间:2023-07-08 07:34:44 阅读: 评论:0

C#(99):⼆、并⾏编程-Task任务
⼆、并⾏编程 - Task任务
任务,基于线程池。其使我们对并⾏编程变得更简单,且不⽤关⼼底层是怎么实现的。
System.Threading.Tasks.Task类是Task Programming Library(TPL)中最核⼼的⼀个类。
⼀、任务与线程
1:任务是架构在线程之上的,也就是说任务最终还是要抛给线程去执⾏。
2:任务跟线程不是⼀对⼀的关系,⽐如开10个任务并不是说会开10个线程,这⼀点任务有点类似线程池,但是任务相⽐线程池有很⼩的开销和精确的控制。
我们⽤VS⾥⾯的“并⾏任务”看⼀看,快捷键Ctrl+D,K,或者找到“调试"->"窗⼝“->"并⾏任务“,我们在WaitAll⽅法处插⼊⼀个断点,最终我们发现任务确实托管给了线程。
⼆、初识Task
两种构建Task的⽅式,只是StartNew⽅法直接构建出了⼀个Task之后⼜调⽤了其Start⽅法。
熬小米粥多长时间
Task.Factory.StartNew (() =>
{
Console.WriteLine("Hello word!");
});
油菜豆腐
Task task =
new Task
(() =>
{
Console.WriteLine("Hello,Word!");
});
task.Start();
在Task内部执⾏的内容我们称作为Task的Body,Task提供了多个初始化重载的⽅法。
public Task(Action action);
public Task(Action<object> action,
object state
);给action传参数
public Task(Action action, CancellationToken cancellationToken);
public Task(Action action, TaskCreationOptions creationOptions);
例如使⽤了重载⽅法的State参数:
Task task2 = new Task((obj ) =>
{ Console.WriteLine("Message: {0}", obj); },
"Say \"Hello\" from task2
");
task2.Start();
补充细节
在创建Task的时候,Task有很多的构造函数的重载,⼀个主要的重载就是传⼊TaskCreateOptions的枚举:
TaskCreateOptions.None:⽤默认的⽅式创建⼀个Task
TaskCreateOptions.PreferFairness:请求scheduler尽量公平的执⾏Task(后续⽂章会将是,Task和线程⼀样,有优先级的)    TaskCreateOptions.LongRunning:声明Task将会长时间的运⾏。
TaskCreateOptions.AttachToParent:因为Task是可以嵌套的,所以这个枚举就是把⼀个⼦task附加到⼀个⽗task中。三、任务的结果
任务结束时,它可以把⼀些有⽤的状态信总写到共享对象中。这个共享对象必须是线程安全的。
另⼀个⽅式是使⽤返回某个结果的任务。使⽤Task类的泛型版本,就可以定义返冋某个结果的任务的返回类型。
使⽤返回值的Result属性可获取是在⼀个Task运⾏完成才会获取的,所以task2是在task1运⾏完成后,才开始运⾏,也就是说上⾯的两个result的值不管运⾏多少次都是不会变的。其中我们也可以通过CurrentId来获取当前运⾏的Task的编号。
var loop = 0;
var task1 = new Task<int>(() =>
{
for (var i = 0; i < 1000; i++)
loop += i;
return loop;
});
task1.Start();
var loopResut = task1.Result;
var task2 = new Task<long>(obj=>
{
long res = 0;
var looptimes = (int)obj;
for (var i = 0; i < looptimes; i++)
res += i;
return res;
},loopResut);
task2.Start();
var resultTask2 = task2.Result;
Console.WriteLine("任务1的结果':{0}\n任务2的结果:{1}",  loopResut,resultTask2);
.
NET 4.5 :Task.Run
在 Framework 4.5 及更⾼版本(包括 Core 和 Standard)中,使⽤静态⽅法作为的快捷⽅式。
Task.Run的跟Task.Factory.StarNew和new Task相差不多,不同的是前两种是放进线程池⽴即执⾏,⽽Task.Run则是等线程池空闲后在执⾏。
Run⽅法只接受⽆参的Action和Func委托,另外两个接受⼀个object类型的参数。
在msdn中TaskFactory.StartNew的备注信息如下:
四、连续任务
所谓的延续的Task就是在第⼀个Task完成后⾃动启动下⼀个Task。我们通过ContinueWith⽅法来创建延续的Task。我们假设有⼀个接受xml解析的服务,⾸先从某个地⽅接受⽂件,然后解析⼊库,最后发送是否解析正确的回执。在每次调⽤ContinueWith⽅法时,每次会把上次Task的引⽤传⼊进来,以便检测上次Task的状态,⽐如我们可以使⽤上次Task的Result属性来获取返回值。
var ReceiveTask  = new Task(() => ReceiveXml());
var ResolveTask = ReceiveTask .ContinueWith <bool>((r) => ResolveXml());
var SendFeedBackTask = ResolveTask.ContinueWith <string>((s) => SendFeedBack(s.Result));
ReceiveTask.Start();
Console.WriteLine(SendFeedBackTask.Result);
上⾯的代码我们也可以这么写:
var SendFeedBackTask = Task.Factory.StartNew(() => ReceiveXml())
.ContinueWith<bool>(s => ResolveXml())
.ContinueWith<string>(r => SendFeedBack(r.Result));
Console.WriteLine(SendFeedBackTask.Result);
⽆论前⼀个任务是如何结束的,前⾯的连续任务总是在前⼀个任务结束时启动。使⽤ TaskContinuationOptions枚举中的值,可以指定,连续任务只有在起始任务成功(或失败)结束吋启动。可能的值是 OnlyOnFaulted、NotOoFaulted、Onl)OnCanceIed、NotOnCanceled 和 OnlyOnRanToCompletion
Task t5 = t1.ContinueWith(DoOnError,
TaskContinuationOptions.OnlyOnFaulted);
五、分离嵌套任务
有些情况下我们需要创建嵌套的Task,嵌套⾥⾯⼜分为分离的和不分离的。其创建的⽅式很简单,就是在Task的body⾥⾯创建⼀个新的Task。如果新的Task未指定AttachedToParent选项,那么就是分离嵌套的。我们看下⾯这段代码。下⾯的代码中outTask.Wait()表⽰等待outTask执⾏完成。
var outTask = Task.Factory.StartNew(() =>
{
Console.WriteLine("Outer ");
var childTask = Task.Factory.StartNew(() =>
{
Thread.SpinWait(3000000);
Console.WriteLine("Detached nested task completed.");
});
});
outTask.Wait();
Console.WriteLine("Outer task completed.");
Console.ReadKey();
我们可以看到运⾏结果是:
六、⼦任务
我们将上⾯的代码加上TaskCreationOptions选项:
如果⽗任务在⼦任务之前结束,⽗任务的状态就显⽰为WaitingForChildrenToComplete。只要⼦任务也结束时,⽗任务的状态就变成RanToCompletion。.、当然,如果⽗任务⽤TaskCreatiooOptions 枚举中的DetachedFromParent创建⼦任务时,这就⽆效。
var outTask = Task.Factory.StartNew(() =>
{
Console.WriteLine("Outer ");
var childTask = Task.Factory.StartNew(() =>
{
Thread.SpinWait(3000000);
Console.WriteLine("Detached nested task completed.");
},TaskCreationOptions.AttachedToParent);
电脑蓝屏了怎么办});
outTask.Wait();
Console.WriteLine("Outer task completed.");
看到运⾏结果:
七、取消任务
在4.0中给我们提供⼀个“取消标记”叫做CancellationTokenSource.Token,在创建task的时候传⼊此参数,就可以将主线程和任务相关联。我们通过cancellation的tokens来取消⼀个Task。
有点要特别注意的,当我们调⽤了Cancel()⽅法之后, Framework不会强制性的去关闭运⾏的Task。我们⾃⼰必须去检测之前在创建Task时候传⼊的那个CancellationToken。
⼀旦cancel被调⽤,task将会抛出OperationCanceledException来中断此任务的执⾏,最后将当前task的Status的IsCanceled属性设为true。
1、在很多Task的Body⾥⾯包含循环,我们可以在轮询的时候判断IsCancellationRequested属性是否为True,如果是True的话,就可
以停⽌循环以及释放资源,同时抛出OperationCanceledException异常出来。
2、或者在任务中设置“取消信号“叫做ThrowIfCancellationRequested,来等待主线程使⽤Cancel来通知。
3、检测task是否被cancel就是调⽤CancellationToken.WaitHandle属性。CancellationToken的WaitOn
e()⽅法会阻⽌task的运⾏,只有CancellationToken的cancel()⽅法被调⽤后,这种阻⽌才会释放。
var cts = new CancellationTokenSource();
var ct = cts.Token;
var task = Task.Factory.StartNew(() =>
张仪连横{
for (var i = 0; i < 10000000; i++)
{
if (ct.IsCancellationRequested)
{
Console.WriteLine("任务开始取消...");
throw new OperationCanceledException(ct);
宣言夫妇
}
//或者直接在检测到异常时,扔出异常:  token.ThrowIfCancellationRequested();
//或者等待 WaitHandle:          token.WaitHandle.WaitOne();
}
},ct);//传⼊CancellationToken作为Task第⼆个参数
ct.Register(() =>
{
Console.WriteLine("已经取消");
});
Thread.Sleep(5000);
cts.Cancel();//如果想要取消⼀个Task的运⾏,只要调⽤CancellationToken实例的Cancel()⽅法就可以了。
try
{
task.Wait();
}
catch (AggregateException e)
{
foreach (var v in e.InnerExceptions)
Console.WriteLine("msg: " + v.Message);
}
⼋、休眠:等待时间执⾏
在TPL中我们可以通过三种⽅式进⾏等待,⼀是通过CancellTaken的WaitHanle进⾏等待、第⼆种则是通过传统的Tread.Sleep⽅法、第三种则通过Thread.SpainWait⽅法。
1、CancellToken⽅式:每次我们等待⼗秒钟之后,再进⾏下次输出。
有⼀点要注意:WaitOne()⽅法只有在设定的时间间隔到了,或者Cancel⽅法被调⽤,此时task才会被唤醒。如果如果cancel()⽅法被调⽤⽽导致task被唤醒,那么CancellationToken.WaitHandle.WaitOne()⽅法就会返回true,如果是因为设定的时间到了⽽导致task唤醒,那么CancellationToken.WaitHandle.WaitOne()⽅法返回fal。
var cts = new CancellationTokenSource();
var ct = cts.Token;
var task = new Task(() =>
{
for (var i = 0; i < 100000; i++)
{
var cancelled = ct.WaitHandle.WaitOne(1000);
Console.WriteLine(" {0}. Cancelled? {1}", i, cancelled);
if (cancelled)
{
throw new OperationCanceledException(ct);
}
}
}, ct);
task.Start();
2、上⾯的功能如果我们要是通过Tread.Sleep⽅式实现:
var task = new Task(() =>
{
for (var i = 0; i < 100000; i++)
{
Thread.Sleep(10000);
var cancelled =ct.IsCancellationRequested;
Console.WriteLine(" {0}. Cancelled? {1}",
i, cancelled); if (cancelled)
{
刀剑神域hthrow new OperationCanceledException(ct);
}
}
},ct);
3、Thread.SpainWait则跟上⾯两种⽅式完全不同,上⾯的两种⽅式都是会在线程调度程序不考虑改线程,直等到运⾏结束。⽽
Thread.SpainWait的作⽤实质上会将处理器置于⼗分紧密的循环中,主要的作⽤是来实现同步锁的作⽤。并不常⽤,⼤部分情况下我们可以通过Lock的⽅式来实现。
Thread.SpinWait(10000);
九、等待任务执⾏
在很多时候我们也许需要等待同时开启的⼏个线程完成之后再来做其他事,在TPL中提供了⼏种⽅式来等待任务执⾏。Task.Wait等待单个任务完成;Task.WaitAll等待所有的Task完成、TaskAny等在其中的任何⼀个或则多个任务完成。
1、Task.Wait: 等待单独的⼀个Task执⾏完成
共有5个重载:Wait()、Wait(CancellToken)、Wait(Int32)、Wait(TimeSpan)、Wait(TimeSpan、CancellToken)。各个重载⽅法的含义:
1)Wait():等待整个任务完成或者取消或者出现异常;
2)Wait(CancellToken):等待任务直到CancellToken调⽤取消或者完成,或者出现异常;
每日的英文3)Wait(Int32):等待任务,未完成则到指定的时间;
4)Wait(TimeSpan):同上;
5)Wait(TimeSpan、CancellToken):等待任务到指定时间,或者CancellToken调⽤取消或者任务完成。
static void Main(string[] args)
徐福墓
{
var tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
Task task = createTask(token,6);    task.Start();
Console.WriteLine("Wait() complete.");
task.Wait();
Console.WriteLine("Task Completed.");

本文发布于:2023-07-08 07:34:44,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/89/1072653.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:任务   等待   线程   取消   完成   创建
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图