9.Task任务类

  1. 9.CSharp各版本新功能和语法-CSharp5功能和语法-Task任务类
    1. 9.1 知识点
      1. 认识Task
      2. 创建无返回值Task的三种方式
        1. 方式一: new Task对象 传入委托函数 并启动
        2. 方式二: Task.Run静态方法 传入委托函数
        3. 方式三: Task.Factory.StartNew静态方法 传入委托函数
      3. 创建有返回值的Task
        1. 方式一: new Task<T>对象 传入委托函数 并启动
        2. 方式二: Task.Run<T>静态方法 传入委托函数
        3. 方式三: Task.Factory.StartNew<T>静态方法 传入委托函数
        4. Task.Result属性 获取返回值
        5. 注意Resut获取结果时会阻塞线程
      4. 同步执行Task
        1. Task.RunSynchronously方法 同步执行Task
      5. Task中线程阻塞的方式(任务阻塞)
        1. 方式一:Task.Wait方法 等待任务执行完毕,再执行后面的内容
        2. 方式二:Task.WaitAny静态方法 传入任务中任意一个任务结束就继续执行
        3. 方式三:Task.WaitAll静态方法 任务列表中所有任务执行结束就继续执行
      6. Task完成后继续其它Task(任务延续)
        1. 方式一:Task.WhenAll方法 + Task.ContinueWith方法 传入任务完毕后再执行某任务
        2. 方式二:Task.Factory.ContinueWhenAll方法 传入任务完毕后再执行某任务
        3. 方式三:Task.WhenAny方法 + Task.ContinueWith方法 传入任务只要有一个执行完毕后再执行某任务
        4. 方式四:Task.Factory.ContinueWhenAny方法 传入任务只要有一个执行完毕后再执行某任务
      7. 取消Task执行
        1. 方法一:通过加入bool标识 控制线程内死循环的结束
        2. 方法二:通过CancellationTokenSource取消标识源类 来控制
      8. 总结
    2. 9.2 知识点代码

9.CSharp各版本新功能和语法-CSharp5功能和语法-Task任务类


9.1 知识点

认识Task

  • 命名空间: System.Threading.Tasks
  • 类名: Task
  • Task顾名思义就是任务的意思
  • Task是在线程池基础上进行的改进,它拥有线程池的优点,同时解决了使用线程池不易控制的弊端
  • 它是基于线程池的优点对线程的封装,可以让我们更方便高效的进行多线程开发

简单理解:

  • Task的本质是对线程Thread的封装,它的创建遵循线程池的优点,并且可以更方便的让我们控制线程
  • 一个Task对象就是一个线程

创建无返回值Task的三种方式

  • 用一个布尔值控制task的执行 当销毁时设置为false不执行逻辑 否则也会和Unity编辑器进程共生死
private bool isRuning = true;
private void OnDestroy()
{
    isRuning = false;
}

方式一: new Task对象 传入委托函数 并启动

//这种方式和普通开启线程方式类似 假如不用isRuning控制也会和Unity编辑器进程共生死
//这种方式是从线程池取出线程 比普通线程性能开销更小
Task task1 = new Task(() =>
{
    int i = 0;
    while (isRuning)
    {
        print("无返回值Task方式一:" + i);
        ++i;
        Thread.Sleep(1000);
    }
});
task1.Start();

方式二: Task.Run静态方法 传入委托函数

//这种方式不需要调用Start方法 直接就能开启
Task task2 = Task.Run(() =>
{
    int i = 0;
    while (isRuning)
    {
        print("无返回值Task方式二:" + i);
        ++i;
        Thread.Sleep(1000);
    }
});

方式三: Task.Factory.StartNew静态方法 传入委托函数

//这种方式也不需要调用Start方法 直接就能开启
Task t3 = Task.Factory.StartNew(() =>
{
    int i = 0;
    while (isRuning)
    {
        print("无返回值Task方式三:" + i);
        ++i;
        Thread.Sleep(1000);
    }
});

创建有返回值的Task

  • 有返回值的task成员
Task<int> task4;
Task<string> task5;
Task<float> task6;

方式一: new Task<T>对象 传入委托函数 并启动

task4 = new Task<int>(() =>
{
    int i = 0;
    while (isRuning)
    {
        print("有返回值方式一:" + i);
        ++i;
        Thread.Sleep(1000);
    }
    return 1;
});
task4.Start();

方式二: Task.Run<T>静态方法 传入委托函数

task5 = Task.Run<string>(() =>
{
    int i = 0;
    while (isRuning)
    {
        print("有返回值方式二:" + i);
        ++i;
        Thread.Sleep(1000);
    }
    return "1231";
});

方式三: Task.Factory.StartNew<T>静态方法 传入委托函数

task6 = Task.Factory.StartNew<float>(() =>
{
    int i = 0;
    while (isRuning)
    {
        print("有返回值方式三:" + i);
        ++i;
        Thread.Sleep(1000);
    }
    return 4.5f;
});

Task.Result属性 获取返回值

void Update()
{
    if (Input.GetKeyDown(KeyCode.Space))
    {
        //停止task 打印task中的返回值
        isRuning = false;
        print(task4.Result);
        print(task5.Result);
        print(task6.Result);
    }
}

注意Resut获取结果时会阻塞线程

Resut获取结果时会阻塞线程
即如果task没有执行完成,会等待task执行完成获取到Result,然后再执行后边的代码
也就是说,执行到这句代码时由于我们的Task中是死循环,所以主线程就会被卡死

就比如说,假如isRuning还是true,直接执行以下代码
由于还在死循环,一直获取不到Result,就一直卡在获取Result的代码上,整个Unity会卡死崩溃

print(task4.Result);
print(task5.Result);
print(task6.Result);

同步执行Task

  • 刚才我们举的例子都是通过多线程异步执行的
  • 如果你希望Task能够同步执行
  • 只需要调用Task对象中的RunSynchronously方法
  • 注意:需要使用 new Task对象的方式,因为Run和StartNew在创建时就会启动,不能通过Run和StartNew来同步执行

Task.RunSynchronously方法 同步执行Task

Task task7 = new Task(() =>
{
    Thread.Sleep(1000);
    print("哈哈哈");
});

//就不使用Start方法了
//task7.Start();

//使用RunSynchronously方法 这样就能和主线程同步执行
task7.RunSynchronously();

//由于开启了同步执行 会先打印哈哈哈 再打印主线程执行 可以通俗理解为把这个task丢到了主线程
print("主线程执行");

Task中线程阻塞的方式(任务阻塞)

方式一:Task.Wait方法 等待任务执行完毕,再执行后面的内容

Task task8 = Task.Run(() =>
{
    for (int i = 0; i < 5; i++)
    {
        print("task8:" + i);
    }
});

Task task9 = Task.Run(() =>
{
    for (int i = 0; i < 20; i++)
    {
        print("task9:" + i);
    }
});

//使用Wait方法后 要等task8的逻辑执行完 才会执行主线程逻辑 但是task9不收影响
task8.Wait();

方式二:Task.WaitAny静态方法 传入任务中任意一个任务结束就继续执行

//传入的至少有一个任务执行完了 才能执行主线程逻辑
Task.WaitAny(task8, task9);

方式三:Task.WaitAll静态方法 任务列表中所有任务执行结束就继续执行

//传入的所有任务执行完了 才能执行主线程逻辑
Task.WaitAll(task8, task8);

Task完成后继续其它Task(任务延续)

方式一:Task.WhenAll方法 + Task.ContinueWith方法 传入任务完毕后再执行某任务

//要完成了WhenAll里的全部任务 再继续ContinueWith里的任务
Task.WhenAll(task8, task9).ContinueWith((task10) =>
{
    print("一个新的任务task10开始了");
    int i = 0;
    while (isRuning)
    {
        print(i);
        ++i;
        Thread.Sleep(1000);
    }
});

方式二:Task.Factory.ContinueWhenAll方法 传入任务完毕后再执行某任务

//要完成了ContinueWhenAll第一个参数里的任务数组中全部任务 再继续ContinueWhenAll第二个参数的任务
Task.Factory.ContinueWhenAll(new Task[] { task8, task9 }, (task11) =>
{
    print("一个新的任务task11开始了");
    int i = 0;
    while (isRuning)
    {
        print(i);
        ++i;
        Thread.Sleep(1000);
    }
});

方式三:Task.WhenAny方法 + Task.ContinueWith方法 传入任务只要有一个执行完毕后再执行某任务

//只要完成了WhenAny里的其中一个任务 就能继续ContinueWith里的任务
Task.WhenAny(task8, task9).ContinueWith((task12) =>
{
    print("一个新的任务task12开始了");
    int i = 0;
    while (isRuning)
    {
        print(i);
        ++i;
        Thread.Sleep(1000);
    }
});

方式四:Task.Factory.ContinueWhenAny方法 传入任务只要有一个执行完毕后再执行某任务

//只要完成了ContinueWhenAny第一个参数里的其中一个任务 就能继续ContinueWhenAll第二个参数的任务
Task.Factory.ContinueWhenAny(new Task[] { task1, task2 }, (task13) =>
{
    print("一个新的任务task13开始了");
    int i = 0;
    while (isRuning)
    {
        print(i);
        ++i;
        Thread.Sleep(1000);
    }
});

取消Task执行

方法一:通过加入bool标识 控制线程内死循环的结束

比如bool isRuning

方法二:通过CancellationTokenSource取消标识源类 来控制

//CancellationTokenSource对象可以达到延迟取消、取消回调等功能

//其中CancellationTokenSource的IsCancellationRequested默认是false

cancellationTokenSource = new CancellationTokenSource();

//延迟取消 延迟5秒 IsCancellationRequested会从false变成true
cancellationTokenSource.CancelAfter(5000);

//取消回调 当IsCancellationRequested从false变成true 执行取消回调
cancellationTokenSource.Token.Register(() =>
{
    print("任务取消了");
});

Task.Run(() =>
{
    int i = 0;
    //IsCancellationRequested默认是false
    while (!cancellationTokenSource.IsCancellationRequested)
    {
        print("计时:" + i);
        ++i;
        Thread.Sleep(1000);
    }
});

void Update()
{
    if (Input.GetKeyDown(KeyCode.Space))
    {
        //Cancel方法能让IsCancellationRequested属性变成true
        cancellationTokenSource.Cancel();
    }
}

总结

  • Task类是基于Thread的封装
  • Task类可以有返回值,Thread没有返回值
  • Task类可以执行后续操作,Thread没有这个功能
  • Task可以更加方便的取消任务,Thread相对更加单一
  • Task具备ThreadPool线程池的优点,更节约性能

9.2 知识点代码

using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

public class Lesson09_CSharp各版本新功能和语法_CSharp5功能和语法_Task任务类 : MonoBehaviour
{
    #region 知识点二 创建无返回值Task的三种方式

    //用一个布尔值控制task的执行 当销毁时设置为false不执行逻辑 否则也会和Unity编辑器进程共生死

    private bool isRuning = true;
    private void OnDestroy()
    {
        isRuning = false;
    }

    #endregion


    #region 知识点三 创建有返回值的Task

    //有返回值的task成员

    Task<int> task4;
    Task<string> task5;
    Task<float> task6;

    #endregion

    # region 知识点七 取消Task执行
    CancellationTokenSource cancellationTokenSource;
    #endregion

    void Start()
    {
        #region 知识点一 认识Task
        //命名空间:System.Threading.Tasks
        //类名:Task
        //Task顾名思义就是任务的意思
        //Task是在线程池基础上进行的改进,它拥有线程池的优点,同时解决了使用线程池不易控制的弊端
        //它是基于线程池的优点对线程的封装,可以让我们更方便高效的进行多线程开发

        //简单理解:
        //Task的本质是对线程Thread的封装,它的创建遵循线程池的优点,并且可以更方便的让我们控制线程
        //一个Task对象就是一个线程
        #endregion

        #region 知识点二 创建无返回值Task的三种方式

        //方式一:通过new一个Task对象传入委托函数并启动

        //这种方式和普通开启线程方式类似 假如不用isRuning控制也会和Unity编辑器进程共生死
        //这种方式是从线程池取出线程 比普通线程性能开销更小
        Task task1 = new Task(() =>
        {
            int i = 0;
            while (isRuning)
            {
                print("无返回值Task方式一:" + i);
                ++i;
                Thread.Sleep(1000);
            }
        });
        task1.Start();

        //方式二:通过Task中的Run静态方法传入委托函数

        //这种方式不需要调用Start方法 直接就能开启
        Task task2 = Task.Run(() =>
        {
            int i = 0;
            while (isRuning)
            {
                print("无返回值Task方式二:" + i);
                ++i;
                Thread.Sleep(1000);
            }
        });

        //方式三:通过Task.Factory中的StartNew静态方法传入委托函数

        //这种方式也不需要调用Start方法 直接就能开启
        Task t3 = Task.Factory.StartNew(() =>
        {
            int i = 0;
            while (isRuning)
            {
                print("无返回值Task方式三:" + i);
                ++i;
                Thread.Sleep(1000);
            }
        });

        #endregion

        #region 知识点三 创建有返回值的Task

        //方式一:通过new一个Task对象传入入委托函数并启动
        task4 = new Task<int>(() =>
        {
            int i = 0;
            while (isRuning)
            {
                print("有返回值方式一:" + i);
                ++i;
                Thread.Sleep(1000);
            }
            return 1;
        });
        task4.Start();

        //方式二:通过Task中的Run静态方法传入委托函数
        task5 = Task.Run<string>(() =>
        {
            int i = 0;
            while (isRuning)
            {
                print("有返回值方式二:" + i);
                ++i;
                Thread.Sleep(1000);
            }
            return "1231";
        });

        //方式三:通过Task.Factory中的StartNew静态方法传入委托函数
        task6 = Task.Factory.StartNew<float>(() =>
        {
            int i = 0;
            while (isRuning)
            {
                print("有返回值方式三:" + i);
                ++i;
                Thread.Sleep(1000);
            }
            return 4.5f;
        });


        //通过Task类的Result属性 获取返回值

        //注意:
        //Resut获取结果时会阻塞线程
        //即如果task没有执行完成
        //会等待task执行完成获取到Result
        //然后再执行后边的代码,也就是说 执行到这句代码时 由于我们的Task中是死循环 
        //所以主线程就会被卡死

        //就比如说 isRuning还是true 直接执行以下代码
        //由于还在死循环 一直获取不到Result 就一直卡在获取Result的代码上 整个Unity会卡死崩溃
        //print(task4.Result);
        //print(task5.Result);
        //print(task6.Result);

        #endregion

        #region 知识点四 同步执行Task

        //刚才我们举的例子都是通过多线程异步执行的
        //如果你希望Task能够同步执行
        //只需要调用Task对象中的RunSynchronously方法
        //注意:需要使用 new Task对象的方式,因为Run和StartNew在创建时就会启动,不能通过Run和StartNew来同步执行

        Task task7 = new Task(() =>
        {
            Thread.Sleep(1000);
            print("哈哈哈");
        });

        //就不使用Start方法了
        //task7.Start();

        //使用RunSynchronously方法 这样就能和主线程同步执行
        task7.RunSynchronously();

        //由于开启了同步执行 会先打印哈哈哈 再打印主线程执行 可以通俗理解为把这个task丢到了主线程
        print("主线程执行");

        #endregion

        #region 知识点五 Task中线程阻塞的方式(任务阻塞)

        //方式一:Wait方法:等待任务执行完毕,再执行后面的内容
        Task task8 = Task.Run(() =>
        {
            for (int i = 0; i < 5; i++)
            {
                print("task8:" + i);
            }
        });

        Task task9 = Task.Run(() =>
        {
            for (int i = 0; i < 20; i++)
            {
                print("task9:" + i);
            }
        });

        //使用Wait方法后 要等task8的逻辑执行完 才会执行主线程逻辑 但是task9不收影响
        task8.Wait();

        //方式二:WaitAny静态方法:传入任务中任意一个任务结束就继续执行
        //传入的至少有一个任务执行完了 才能执行主线程逻辑
        Task.WaitAny(task8, task9);

        //方式三:WaitAll静态方法:任务列表中所有任务执行结束就继续执行
        //传入的所有任务执行完了 才能执行主线程逻辑
        Task.WaitAll(task8, task8);

        //print("主线程执行");
        #endregion

        #region 知识点六 Task完成后继续其它Task(任务延续)

        //方式一:WhenAll静态方法 + ContinueWith方法:传入任务完毕后再执行某任务

        //要完成了WhenAll里的全部任务 再继续ContinueWith里的任务
        Task.WhenAll(task8, task9).ContinueWith((task10) =>
        {
            print("一个新的任务task10开始了");
            int i = 0;
            while (isRuning)
            {
                print(i);
                ++i;
                Thread.Sleep(1000);
            }
        });


        //方式二:Factory中的ContinueWhenAll方法:传入任务完毕后再执行某任务

        //要完成了ContinueWhenAll第一个参数里的任务数组中全部任务 再继续ContinueWhenAll第二个参数的任务
        Task.Factory.ContinueWhenAll(new Task[] { task8, task9 }, (task11) =>
        {
            print("一个新的任务task11开始了");
            int i = 0;
            while (isRuning)
            {
                print(i);
                ++i;
                Thread.Sleep(1000);
            }
        });

        //方式三:WhenAny静态方法 + ContinueWith方法:传入任务只要有一个执行完毕后再执行某任务

        //只要完成了WhenAny里的其中一个任务 就能继续ContinueWith里的任务
        Task.WhenAny(task8, task9).ContinueWith((task12) =>
        {
            print("一个新的任务task12开始了");
            int i = 0;
            while (isRuning)
            {
                print(i);
                ++i;
                Thread.Sleep(1000);
            }
        });

        //方式四:Factory中的ContinueWhenAny方法:传入任务只要有一个执行完毕后再执行某任务

        //只要完成了ContinueWhenAny第一个参数里的其中一个任务 就能继续ContinueWhenAll第二个参数的任务
        Task.Factory.ContinueWhenAny(new Task[] { task1, task2 }, (task13) =>
        {
            print("一个新的任务task13开始了");
            int i = 0;
            while (isRuning)
            {
                print(i);
                ++i;
                Thread.Sleep(1000);
            }
        });
        #endregion

        #region 知识点七 取消Task执行

        //方法一:通过加入bool标识 控制线程内死循环的结束
        //比如bool isRuning

        //方法二:通过CancellationTokenSource取消标识源类 来控制

        //CancellationTokenSource对象可以达到延迟取消、取消回调等功能

        //其中CancellationTokenSource的IsCancellationRequested默认是false

        cancellationTokenSource = new CancellationTokenSource();

        //延迟取消 延迟5秒 IsCancellationRequested会从false变成true
        cancellationTokenSource.CancelAfter(5000);

        //取消回调 当IsCancellationRequested从false变成true 执行取消回调
        cancellationTokenSource.Token.Register(() =>
        {
            print("任务取消了");
        });

        Task.Run(() =>
        {
            int i = 0;
            //IsCancellationRequested默认是false
            while (!cancellationTokenSource.IsCancellationRequested)
            {
                print("计时:" + i);
                ++i;
                Thread.Sleep(1000);
            }
        });

        #endregion

        #region 总结
        //1.Task类是基于Thread的封装
        //2.Task类可以有返回值,Thread没有返回值
        //3.Task类可以执行后续操作,Thread没有这个功能
        //4.Task可以更加方便的取消任务,Thread相对更加单一
        //5.Task具备ThreadPool线程池的优点,更节约性能
        #endregion
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            //停止task 打印task中的返回值
            isRuning = false;
            print(task4.Result);
            print(task5.Result);
            print(task6.Result);

            //Cancel方法能让IsCancellationRequested属性变成true
            cancellationTokenSource.Cancel();
        }
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏