0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

多线程之Task

汽车电子技术 来源:程序猿知秋 作者:程序猿知秋 2023-02-28 09:52 次阅读

图片

什么是Task?

描述

  • Task出现之前,微软的多线程处理方式有:Thread→ThreadPool→委托的异步调用,虽然可以满足基本业务场景,但它们在多个线程的等待处理方面、资源占用方面、延续和阻塞方面都显得比较笨拙,在面对复杂的业务场景下,显得有点捉襟见肘
  • Task是微软在.Net 4.0时代推出来的,也是微软极力推荐的一种多线程的处理方式,Task看起来像一个Thread,实际上,它是在ThreadPool的基础上进行的封装
  • Task的控制和扩展性很强,在线程的延续、阻塞、取消、超时等方面远胜于Thread和ThreadPool
  • Task可以简单看作相当于Thead+TheadPool,其性能比直接使用Thread要更好,在工作中更多的是使用Task来处理多线程任务

任务Task和线程Thread的区别

  • Task是建立在Thread之上的,最终其实还是由Thread去执行,它们都是在 System.Threading 命名空间下的
  • Task跟Thread并不是一对一的关系。比如说开启10个任务并不一定会开启10个线程,因为使用Task开启新任务时,是从线程池中调用线程,这点与ThreadPool.QueueUserWorkItem类似

Task的使用

创建Task的三种方式

  • 方式一:通过创建Task对象后调用其 Start()函数
  • 方式二:调用Task的静态方法Run()
  • 方式三:通过Task工厂,新建一个线程
// 方式一,通过Start
Task t1 = new Task(() => { Console.WriteLine("我是Task方式一"); });
t1.Start();


// 方式二,通过Run
Task t2= Task.Run(()=>{Console.WriteLine("我是Task方式二"); });


// 方式三,通过工厂
Task t3= Task.Factory.StartNew(()=>{Console.WriteLine("我是Task方式三"); });

带返回值与不带返回值的Task

  • Task无返回值: 接收的是Action委托类型
  • Task有返回值: 接收的是Func委托类型
static void Main()
{
  // 没有返回参数
  Task t1 = new Task(() => { Console.WriteLine("我是Task没有返回参数"); });
  t1.Start();


  // 有返回参数
  Task<int> t2 = new Task<int>(() => { return 1+1; });
  t2.Start();
  int result = t2.Result;
  Console.WriteLine(result);
}

输出结果

我是Task没有返回参数
2

一次性建立多个任务场景

static void test1()
{
  Task[] taskArray = new Task[10];
  for (int i = 0; i < 10; i++)
  {
    int bb = i;
    Task t = Task.Run(() => { Console.WriteLine("任务ID:{0}, 结果:{1}",Thread.CurrentThread.ManagedThreadId, bb); });
    taskArray[i] = t;
  }
  // 等待所有任务完成
  Task.WaitAll(taskArray);
}

输出结果

任务ID:4, 结果:0
任务ID:10, 结果:4
任务ID:7, 结果:1
任务ID:8, 结果:2
任务ID:10, 结果:7
任务ID:11, 结果:5
任务ID:9, 结果:3
任务ID:12, 结果:6
任务ID:7, 结果:8
任务ID:8, 结果:9

Task阻塞的三种方式

  • Wait(): 等待单个线程任务完成
  • WaitAll():来指定等待的一个或多个线程结束
  • WaitAny():来指定等待任意一个线程任务结束
static void test3()
{
  // 方式一: wait方法
  Task t = Task.Run(() => { Console.WriteLine("方式1:任务1......"); }) ;
  // 等待 上述任务完成
  t.Wait();
  Console.WriteLine("方式一结束..........");


  // 方式二: waitAll 方法
  Task tt = Task.Run(() => { Console.WriteLine("方式2:任务1......"); });
  Task tt2 = Task.Run(() => { Console.WriteLine("方式2:任务2......"); });
  Task.WaitAll(tt,tt2);
  Console.WriteLine("方式二结束..........");


  // 方式三:waitAny 方法
  Task ttt = Task.Run(() => { Console.WriteLine("方式3:任务1......"); });
  Task ttt2 = Task.Run(() => { Console.WriteLine("方式3:任务2......"); });
  Task.WaitAny(ttt, ttt2);
  Console.WriteLine("方式三结束..........");


}

输出结果

方式1:任务1......
方式一结束..........
方式2:任务1......
方式2:任务2......
方式二结束..........
方式3:任务2......
方式3:任务1......
方式三结束..........

Task任务的延续

  • WhenAll().ContinueWith() : 作用是当 WhenAll() 中指定的线程任务完成后再执行 ContinueWith() 中的任务,也就是线程任务的延续。而由于这个等待是异步的,因此不会给主线程造成阻塞
  • WhenAll(task1,task2,...): Task的静态方法,作用是异步等待指定任务完成后,返回结果。当线程任务有返回值时,返回Task对象,否则返回Task对象。
  • WhenAny() :用法与WhenAll()是一样的,不同的是只要指定的任意一个线程任务完成则立即返回结果。
  • ContinueWith(): Task类的实例方法,异步创建当另一任务完成时可以执行的延续任务。也就是当调用对象的线程任务完成后,执行ContinueWith()中的任务
static void test4()
{
  Task t = Task.Run(() => { Thread.Sleep(1000); Console.WriteLine("我是线程任务....."); });
  // 异步创建延续任务
  Task.WhenAll(t).ContinueWith((data) => { Console.WriteLine("我是延续任务...."); });


  Console.WriteLine("这是主线程........");
  Console.ReadKey();


}

输出结果

这是主线程........
我是线程任务.....
我是延续任务....

注:Task任务的延续 与 上面阻塞相比,主要的好处就是 延续是异步的不会阻塞主线程

Task的父子任务

  • TaskCreationOptions.AttachedToParent: 用于将子任务依附到父任务中
static void test5()
{
  // 建立一个父任务
  Task parentTask = new Task(() => {
    // 创建两个子任务,依附在父任务上
    Task.Factory.StartNew(() => { Console.WriteLine("子task1任务。。。。。。"); }, TaskCreationOptions.AttachedToParent);
    Task.Factory.StartNew(() => { Console.WriteLine("子task2任务。。。。。。"); }, TaskCreationOptions.AttachedToParent);
    Thread.Sleep(1000);
    Console.WriteLine("我是父任务........");
  });
  parentTask.Start();
  parentTask.Wait();
  Console.WriteLine("这里是主线程.......");
  Console.ReadKey();
}

输出结果

子task2任务。。。。。。
子task1任务。。。。。。
我是父任务........
这里是主线程.......

**Task中的任务取消 **

Task中的取消功能使用的是CanclelationTokenSource,即取消令牌源对象,可用于解决多线程任务中协作取消和超时取消

  • CancellationToken Token: CanclelationTokenSource类的属性成员,返回CancellationToken对象,可以在开启或创建线程时作为参数传入。
  • IsCancellationRequested: 表示当前任务是否已经请求取消。Token类中也有此属性成员,两者互相关联。
  • Cancel(): CanclelationTokenSource类的实例方法,取消线程任务,同时将自身以及关联的Token对象中的IsCancellationRequested属性置为true。
  • CancelAfter(int millisecondsDelay) :CanclelationTokenSource类的实例方法,用于延迟取消线程任务。

取消任务的两种情况

  • 情况一: 通过Cancel()方法
  • 情况二: 通过CancelAfter(milliseconds) 方法
static void test6()
{
  // 情况一: 直接取消
  // 创建取消令牌源对象
  CancellationTokenSource cst = new CancellationTokenSource();
  //第二个参数传入取消令牌
  Task t = Task.Run(() => {
    while (!cst.IsCancellationRequested)
    {
      Thread.Sleep(500);
      Console.WriteLine("情况一,没有接收到取消信号......");
    }
  }, cst.Token);


  Thread.Sleep(1000);
  //1秒后结束
  cst.Cancel(); 
  Console.ReadKey();




  // 情况二: 延迟取消
  CancellationTokenSource cst2 = new CancellationTokenSource();
  Task t2 = Task.Run(() => {
    while (!cst2.IsCancellationRequested)
    {
      Console.WriteLine("情况二,没有接收到取消信号......");
    }
  }, cst2.Token);
  //1秒后结束
  cst2.CancelAfter(1000);
  Console.ReadKey();
}

**Task跨线程访问界面控件 **

通过 TaskScheduler.FromCurrentSynchronizationContext() 获取TaskScheduler,并将其放入Task的start() 方法中 或 放入延续方法中即可

  • 放入start() 方法中
  • 放入 ContinueWith() 延续方法中
// 通过start方法
private void button1_Click(object sender, EventArgs e)
{
    Task t = new Task(() =>
    {
        // 为界面控件赋值
    this.textBox1.Text = "线程内赋值";
    });

    task.Start(TaskScheduler.FromCurrentSynchronizationContext());
}




// 通过延续方法
private void button1_Click(object sender, EventArgs e)
{
    Task.Run(() =>
    {
      Thread.Sleep(1000);
    }).ContinueWith(t => {
      this.textBox1.Text = "线程内赋值";
    }, TaskScheduler.FromCurrentSynchronizationContext());
}

Task的异常处理

异常捕获

  • Task线程的异常处理 不能直接将线程对象相关代码try-catch来捕获 ,需要通过调用线程对象的wait()函数来进行线程的异常捕获
  • 线程的异常会聚合到AggregateException异常对象中(AggregateException是专门用来收集线程异常的异常类),多个异常 需要通过遍历该异常对象来获取异常信息
  • 如果捕获到线程异常之后,还想继续往上抛出,就需要调用AggregateException对象的Handle函数,并返回false。(Handle函数遍历了一下AggregateException对象中的异常)
static void test7()
{
  Task t = Task.Run(() =>
  {
    throw new Exception("异常抛出.....");
  });


  try
  {
    t.Wait();
  }
  catch (AggregateException ex)
  {
    Console.Error.WriteLine(ex.Message);


    foreach (var item in ex.InnerExceptions)
    {
      Console.WriteLine("内异常:"+item.Message);
    }
    //将异常往外抛出
    // ex.Handle(p => false);
  }
  Console.ReadKey();
}

输出结果

One or more errors occurred. (异常抛出.....)
内异常:异常抛出.....
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 多线程
    +关注

    关注

    0

    文章

    271

    浏览量

    19724
  • Thread
    +关注

    关注

    2

    文章

    83

    浏览量

    25700
  • 调用
    +关注

    关注

    0

    文章

    8

    浏览量

    3171
收藏 人收藏

    评论

    相关推荐

    多线程程之一: 问题提出

    多线程程之一 问题提出编写一个耗时的单线程程序:  新建一个基于对话框的应用程序SingleThread,在主对话框IDD_SINGLETHREAD_DIALOG添加一个按钮,ID为
    发表于 10-22 11:41

    多线程程之线程间通讯

    多线程程之线程间通讯七、线程间通讯  一般而言,应用程序中的一个次要线程总是为主线程执行特
    发表于 10-22 11:43

    多线程程之线程的同步

    多线程程之线程的同步八、线程的同步  虽然多线程能给我们带来好处,但是也有不少问题需要解决。例如,对于像磁盘驱动器这样独占性系统资源,
    发表于 10-22 11:43

    多核CPU下的多线程编程

    多线程编程是现代软件技术中很重要的一个环节。要弄懂多线程,这就要牵涉到多进程?当然,要了解到多进程,就要涉及到操作系统。不过大家也不要紧张,听我慢慢道来。这其中的环节其实并不复杂。 (1)单
    发表于 12-26 15:17

    Linux c多线程编程的4个实例

    [table][tr][td]在主流的操作系统中,多任务一般都提供了进程和线程两种实现方式,进程享有独立的进程空间,而线程相对于进程来说是一种更加轻量级的多任务并行,多线程之间一般都是共享所在进程
    发表于 10-17 09:32

    Linux c多线程编程的4个实例分享

    Linux c多线程编程的4个实例  在主流的操作系统中,多任务一般都提供了进程和线程两种实现方式,进程享有独立的进程空间,而线程相对于进程来说是一种更加轻量级的多任务并行,多线程之
    发表于 06-09 04:35

    多线程解决思路一

    使用方法节点实现多线程,两个线程之间的数据传输也都使用方法节点的方式实现。1、初始化时打开另一个线程。2、程序运行过程中实现对被调线程的实施读写。3、Write data按下后写入对应
    发表于 07-06 17:21

    多线程的过程程序

    1、多线程了解线程之前我们必须要先了解(程序—>进程—>线程)的过程程序:是一组计算机能识别和执行的指令,运行于电子计算机上,满足人们某种需求的信息化工具。进程:进程指正在运行的程序
    发表于 08-24 08:28

    Delphi教程之多线程与数据库

    Delphi教程之多线程与数据库,很好的Delphi资料,快来下载学习吧。
    发表于 04-11 15:59 5次下载

    多线程好还是单线程好?单线程多线程的区别 优缺点分析

    摘要:如今单线程多线程已经得到普遍运用,那么到底多线程好还是单线程好呢?单线程多线程的区别又
    发表于 12-08 09:33 8w次阅读

    mfc多线程编程实例及代码,mfc多线程间通信介绍

    摘要:本文主要以MFC多线程为中心,分别对MFC多线程的实例、MFC多线程之间的通信展开的一系列研究,下面我们来看看原文。
    发表于 12-08 15:23 1.7w次阅读
    mfc<b class='flag-5'>多线程</b>编程实例及代码,mfc<b class='flag-5'>多线程</b>间通信介绍

    什么是多线程编程?多线程编程基础知识

    摘要:多线程编程是现代软件技术中很重要的一个环节。要弄懂多线程,这就要牵涉到多进程。本文主要以多线程编程以及多线程编程相关知识而做出的一些结论。
    发表于 12-08 16:30 1.2w次阅读

    从CPU说起多线程以及线程

    从这篇开始将会开启高性能、高并发系列,本篇是该系列的开篇,主要关注多线程以及线程池。 一切要从CPU说起 你可能会有疑问,讲多线程为什么要从CPU说起呢?原因很简单,在这里没有那些时髦的概念,你可以
    的头像 发表于 03-02 13:48 1776次阅读

    多线程启动停止暂停继续

    单核CPU上所谓的”多线程”那是假的多线程,同一时间处理器只会处理一段逻辑,只不过线程之间切换得比较快,看着像多个线程”同时”运行罢了。
    的头像 发表于 08-11 15:47 1531次阅读
    <b class='flag-5'>多线程</b>启动停止暂停继续

    PyQT5+OpenCV多线程协作演示

    学习多线程最典型的问题就是如何在多个线程之间传递消息与写作,PyQT5的线程支持在不同线程之间传递信号触发事件,实现多个线程之间的协助,完成
    的头像 发表于 03-08 14:58 841次阅读