【多线程笔记】任务-Task
简介
Task(任务)可以解决多线程编程中的复杂性。
多线程编程的复杂性
- 传递数据和返回结果
传递数据倒是没啥问题,只是难以获取到线程的返回值,处理线程的异常也需要技巧。 - 监控线程的状态
新建新的线程后,如果需要确定新线程在何时完成,需要自旋或阻塞等方式等待。 - 线程安全
设计时要考虑如果避免死锁、合理使用各种同步锁,要考虑原子操作,同步信号的处理需要技巧。 - 性能
多线程最大需求就是提升性能,但是多线程中有很多坑,使用不当反而影响性能。
多线程编程模式
.NET 中,有三种异步编程模式,分别是基于任务的异步模式(TAP)、基于事件的异步模式(EAP)、异步编程模式(APM)。
基于任务的异步模式 (TAP) :.NET 推荐使用的异步编程方法,该模式使用单一方法表示异步操作的开始和完成。包括我们常用的 async
、await
关键字,属于该模式的支持。
其他两种.NET Core 已经不支持,忽略。
创建任务的几种方式
1、new Task()
Task task1 = new Task(MyTask);
// 开始任务
task1.Start();
2、Task.Factory
Factory.StartNew(Action action);
Factory.StartNew(Action action, TaskCreationOptions creationOptions);
3、Task.Run()
Task.Run(() =>{});
取消任务
static void Main()
{
Console.WriteLine("任务开始启动,按下任意键,取消执行任务");
CancellationTokenSource cts = new CancellationTokenSource();
Task.Factory.StartNew(MyTask, cts.Token, new TaskCreationOptions());
Console.ReadKey();
cts.Cancel(); // 取消任务
Console.WriteLine("已取消");
Console.ReadKey();
}
private static void MyTask(object state)
{
var token = (CancellationToken)state;
Console.WriteLine(" 开始执行");
int i = 0;
while (!token.IsCancellationRequested)
{
Console.WriteLine($" 第{i}次任务");
Thread.Sleep(TimeSpan.FromSeconds(1));
Console.WriteLine(" 执行结束");
i++;
}
}
任务状态
属性 | 说明 |
---|---|
IsCanceled | 获取此 Task 实例是否由于被取消的原因而已完成执行。只要任务抛出 OperationCancelExcetion 异常,表示任务取消,但不代表任务出错。 |
IsCompleted | 获取一个值,它表示是否已完成任务。 即使任务抛出了未经处理的异常,也算是完成了任务 |
IsCompletedSuccessfully | 了解任务是否运行到完成。 |
IsFaulted | 获取 Task是否由于未经处理异常的原因而完成。只要任务抛出异常,IsFaulted 为 true。(除了 OperationCancelExcetion 异常) |
Status | 获取此任务的 TaskStatus。 |
创建Task参数类TaskCreationOptions
TaskCreationOptions.AttachedToParent
:子任务执行完后,父任务才会继续执行。否则父任务不会等子任务执行完成
Task t = new Task(() =>
{
var t1 = new Task(() =>
{
Thread.Sleep(1000);
Console.WriteLine("t1 end");
}, TaskCreationOptions.AttachedToParent);
var t2 = new Task(() =>
{
Thread.Sleep(1000);
Console.WriteLine("t2 end");
}, TaskCreationOptions.AttachedToParent);
t1.Start();
t2.Start();
});
t.Start();
t.Wait();
Console.WriteLine("主线程end");
//输出结果 t1 end、t2 end、主线程end
- TaskCreationOptions.DenyChildAttach:不允许子任务附加到当前任务
Task t = new Task(() =>
{
var t1 = new Task(() =>
{
Thread.Sleep(1000);
Console.WriteLine("t1 end");
}, TaskCreationOptions.AttachedToParent);
var t2 = new Task(() =>
{
Thread.Sleep(1000);
Console.WriteLine("t2 end");
}, TaskCreationOptions.AttachedToParent);
t1.Start();
t2.Start();
}, TaskCreationOptions.DenyChildAttach);
t.Start();
t.Wait();
Console.WriteLine("主线程end");
//输出结果 主线程end、t1 end、t2 end
- TaskCreationOptions.HideScheduler:子任务默认不使用父任务的Scheduler,而是使用默认的
- TaskCreationOptions.LongRunning:如果你知道任务是长时间运行,就使用此选项,不使用TreadPool
- TaskCreationOptions.PreferFairness:队列?
ContinueWith()
参数类TaskContinuationOptions
TaskCreationOptions中有的属性它都有,就不介绍了
- TaskContinuationOptions.LazyCancellation:cancell延时执行,默认情况下在执行ContinueWith方法中就判断cancell状态。加上此选项后,在Action中判断cancell状态
var tokenSource = new CancellationTokenSource();
tokenSource.Cancel();
Task t1 = new Task(() =>
{
Thread.Sleep(1000);
Console.WriteLine("t1 end");
});
var t2 = t1.ContinueWith((t) =>
{
Thread.Sleep(1000);
Console.WriteLine("t2 end");
}, tokenSource.Token,TaskContinuationOptions.LazyCancellation,TaskScheduler.Current);
var t3 = t2.ContinueWith((t) =>
{
Console.WriteLine("t3 end");
});
t1.Start();
//执行结果:t1 end、t3 end
//如果不加TaskContinuationOptions.LazyCancellation,执行结果是:t3 end、t1 end
- TaskContinuationOptions.ExecuteSynchronously:延续任务使用之前任务的线程执行
//TaskContinuationOptions.OnlyOnCanceled:只有在之前任务取消,才执行
//TaskContinuationOptions.OnlyOnFaulted:只有在之前任务异常,才执行
//TaskContinuationOptions.OnlyOnRanToCompletion:只有在之前任务正常完成,才执行
//TaskContinuationOptions.NotOnCanceled:只有在之前任务没取消,才执行
//TaskContinuationOptions.NotOnFaulted:只有在之前任务没异常,才执行
//TaskContinuationOptions.NotOnRanToCompletion:只有在之前任务没正常完成,才执行
Task.GetAwaiter()和await Task 的区别?
- 加上await关键字之后,后面的代码会被挂起等待,直到task执行完毕有返回值的时候才会继续向下执行,这一段时间主线程会处于挂起状态。
- GetAwaiter方法会返回一个awaitable的对象(继承了INotifyCompletion.OnCompleted方法)我们只是传递了一个委托进去,等task完成了就会执行这个委托,但是并不会影响主线程,下面的代码会立即执行。这也是为什么我们结果里面第一句话会是 “主线程执行完毕”!
await 实质是在调用awaitable对象的GetResult方法
static async Task Test(){
Task<string> task = Task.Run(() =>{
Console.WriteLine("另一个线程在运行!"); // 这句话只会被执行一次
Thread.Sleep(2000);
return "Hello World";
});
// 这里主线程会挂起等待,直到task执行完毕我们拿到返回结果
var result = task.GetAwaiter().GetResult();
// 这里不会挂起等待,因为task已经执行完了,我们可以直接拿到结果
var result2 = await task;
Console.WriteLine(str);
}