【多线程笔记】任务-Task

简介

Task(任务)可以解决多线程编程中的复杂性。

多线程编程的复杂性

  • 传递数据和返回结果
    传递数据倒是没啥问题,只是难以获取到线程的返回值,处理线程的异常也需要技巧。
  • 监控线程的状态
    新建新的线程后,如果需要确定新线程在何时完成,需要自旋或阻塞等方式等待。
  • 线程安全
    设计时要考虑如果避免死锁、合理使用各种同步锁,要考虑原子操作,同步信号的处理需要技巧。
  • 性能
    多线程最大需求就是提升性能,但是多线程中有很多坑,使用不当反而影响性能。

多线程编程模式

.NET 中,有三种异步编程模式,分别是基于任务的异步模式(TAP)、基于事件的异步模式(EAP)、异步编程模式(APM)。
基于任务的异步模式 (TAP) :.NET 推荐使用的异步编程方法,该模式使用单一方法表示异步操作的开始和完成。包括我们常用的 asyncawait 关键字,属于该模式的支持。
其他两种.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

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);
}
posted @ 2019-12-08 18:01  .Neterr  阅读(2366)  评论(0编辑  收藏  举报