首页 > 其他分享 >Task基本用法

Task基本用法

时间:2023-11-04 21:22:45浏览次数:42  
标签:基本 Task Console Thread 用法 线程 WriteLine 执行

 什么是异步

同步和异步主要用于修饰方法。当一个方法被调用时,调用者需要等待该方法执行完毕并返回才能继续执行,这个为异步方法。当一个方法被调用时立即返回,并获取一个线程执行该方法内部的业务,调用者不用等待该方法执行完毕,我们称为这个方法为异步方法。

 《Illustrated C#7 Fifth Edition》解释:启动程序时,系统会在内存中创建一个新的进程。进程是构成运行程序的资源集合。这些资源包括:虚地址控件 文件句柄 和程序文件所需的其他许多东西。在异步程序中,程序不需要按照顺序执行,有时在一个新线程运行,有时无须创建新线程,但为更好利用单线程能力,需要改变代码执行顺序。

Task介绍

task是在ThreadPool基础上推出,ThreadPool中有许多的线程,如果有任务需求处理时,会从线程池中获取一个空闲的线程来执行任务,任务执行完毕线程不会销毁,而是被线程池回收以供后续使用。当线程池中的所有线程都在忙碌时,又有新任务要处理时,线程池才会创建一个线程来处理任务,如果线程池中的线程到达设置的上限,任务会排队 等待其他任务完成而释放的线程。线程池能减少线程的创建,节省开销。

static void Main(string[] args)
{
  for (int i = 1;i<=10,i++)
  {
    ThreadPool.QueueUserWorkItem(new WaitCallback((obj) => {
    Console.WriteLines("....");}),i)
  }

 ThreadPool相对于Thread来说减少线程的创建,有效减少系统开销,但是ThreadPool不能控制线程执行的顺序,我们也不能获取线程池内线程 取消/异常/完成的通知,即我们不能有效监控和控制线程池中的线程

Task创建和运行

Task创建的基本三种形式

          static void Main(string[] args)
        {
            //1.new方式实例化一个Task,需要通过Start方法启动
            Task task = new Task(() =>
            {
                Thread.Sleep(100);
                Console.WriteLine($"hello, task1的线程ID为{Thread.CurrentThread.ManagedThreadId}");
            });
            task.Start();

            //2.Task.Factory.StartNew(Action action)创建和启动一个Task
            Task task2 = Task.Factory.StartNew(() =>
              {
                  Thread.Sleep(100);
                  Console.WriteLine($"hello, task2的线程ID为{ Thread.CurrentThread.ManagedThreadId}");
              });

            //3.Task.Run(Action action)将任务放在线程池队列,返回并启动一个Task
            Task task3 = Task.Run(() =>
              {
                  Thread.Sleep(100);
                  Console.WriteLine($"hello, task3的线程ID为{ Thread.CurrentThread.ManagedThreadId}");
              });
            Console.WriteLine("执行主线程!");
            Console.ReadKey();
        }

 

执行结果

 

创建有返回值的Task

 static void Main(string[] args)
        {
            1.new方式实例化一个Task,需要通过Start方法启动
            Task<string> task = new Task<string>(() =>
            {
                return $"hello, task1的ID为{Thread.CurrentThread.ManagedThreadId}";
            });
            task.Start();

            2.Task.Factory.StartNew(Func func)创建和启动一个Task
           Task<string> task2 =Task.Factory.StartNew<string>(() =>
            {
                return $"hello, task2的ID为{ Thread.CurrentThread.ManagedThreadId}";
            });

            3.Task.Run(Func func)将任务放在线程池队列,返回并启动一个Task
           Task<string> task3= Task.Run<string>(() =>
            {
                return $"hello, task3的ID为{ Thread.CurrentThread.ManagedThreadId}";
            });

            Console.WriteLine("执行主线程!");
            Console.WriteLine(task.Result);
            Console.WriteLine(task2.Result);
            Console.WriteLine(task3.Result);
            Console.ReadKey();
        }

 

注意task.result获取结果时会阻塞主线程,即如果task没有执行完成,会等待task执行完成获取Result,然后在执行后边的代码。

程序运行结果如下

 

 上面的例子Task都是异步的不会阻塞主线程,有些场景下,我们想让Task同步执行,Task提供了Task。RunSynchronously()用于同步执行Task任务

 static void Main(string[] args)
        {
            Task task = new Task(() =>
            {
                Thread.Sleep(100);
                Console.WriteLine("执行Task结束!");
            });
            //同步执行,task会阻塞主线程
            task.RunSynchronously();
            Console.WriteLine("执行主线程结束!");
            Console.ReadKey();
        }

 

执行结果

 Task的阻塞方法(Wait/WaitAll/WaitAny)

使用Thread时,thread.join()会阻塞主线程。看一个例子:

  static void Main(string[] args)
        {
            Thread th1 = new Thread(() => {
                Thread.Sleep(500);
                Console.WriteLine("线程1执行完毕!");
            });
            th1.Start();
            Thread th2 = new Thread(() => {
                Thread.Sleep(1000);
                Console.WriteLine("线程2执行完毕!");
            });
            th2.Start();
            //阻塞主线程
            th1.Join();
            th2.Join();
            Console.WriteLine("主线程执行完毕!");
            Console.ReadKey();
        }

task.Wait()表示等待task执行完毕,功能类似于thread.Join()。请看下面Task.WaitAll()的例子:

 static void Main(string[] args)
        {
            Task task1 = new Task(() => {
                Thread.Sleep(500);
                Console.WriteLine("线程1执行完毕!");
            });
            task1.Start();
            Task task2 = new Task(() => {
                Thread.Sleep(1000);
                Console.WriteLine("线程2执行完毕!");
            });
            task2.Start();
            //阻塞主线程。task1,task2都执行完毕再执行主线程
       //执行【task1.Wait();task2.Wait();】可以实现相同功能
            Task.WaitAll(new Task[]{ task1,task2});
            Console.WriteLine("主线程执行完毕!");
            Console.ReadKey();
        }

Task的延续操作(WhenAny/WhenAll/ContinueWith)

上边的Wait/WaitAll/WaitAny方法返回值为void,这些方法单纯的实现阻塞线程。我们现在想让所有task执行完毕(或者任一task执行完毕)后,开始执行后续操作,怎么操作?这时就可以用到WhenAny/WhenAll方法了,这些方法执行完成返回一个task实例。 task.WhenAll(Task[] tasks) 表示所有的task都执行完毕后再去执行后续的操作, task.WhenAny(Task[] tasks) 表示任一task执行完毕后就开始执行后续操作。看一个栗子:

 static void Main(string[] args)
        {
            Task task1 = new Task(() => {
                Thread.Sleep(500);
                Console.WriteLine("线程1执行完毕!");
            });
            task1.Start();
            Task task2 = new Task(() => {
                Thread.Sleep(1000);
                Console.WriteLine("线程2执行完毕!");
            });
            task2.Start();
            //task1,task2执行完了后执行后续操作
            Task.WhenAll(task1, task2).ContinueWith((t) => {
                Thread.Sleep(100);
                Console.WriteLine("执行后续操作完毕!");
            });

            Console.WriteLine("主线程执行完毕!");
            Console.ReadKey();
        }

执行结果如下,我们看到WhenAll/WhenAny方法不会阻塞主线程,当使用WhenAll方法时所有的task都执行完毕才会执行后续操作;如果把栗子中的WhenAll替换成WhenAny,则只要有一个线程执行完毕就会开始执行后续操作,这里不再演示。

 

上边的栗子也可以通过 Task.Factory.ContinueWhenAll(Task[] tasks, Action continuationAction)和 Task.Factory.ContinueWhenAny(Task[] tasks, Action continuationAction) 来实现 ,修改上边栗子代码如下,执行结果不变

 

   static void Main(string[] args)
        {
            Task task1 = new Task(() => {
                Thread.Sleep(500);
                Console.WriteLine("线程1执行完毕!");
            });
            task1.Start();
            Task task2 = new Task(() => {
                Thread.Sleep(1000);
                Console.WriteLine("线程2执行完毕!");
            });
            task2.Start();
            //通过TaskFactroy实现
            Task.Factory.ContinueWhenAll(new Task[] { task1, task2 }, (t) =>
            {
                Thread.Sleep(100);
                Console.WriteLine("执行后续操作");
            });

            Console.WriteLine("主线程执行完毕!");
            Console.ReadKey();
        }

Task的任务取消(CancellationTokenSource)

在Task前我们执行任务采用的是Thread,Thread怎么取消任务呢?一般流程是:设置一个变量来控制任务是否停止,如设置一个变量isStop,然后线程轮询查看isStop,如果isStop为true就停止,代码如下:

static void Main(string[] args)
        {
            bool isStop = false;
            int index = 0;
            //开启一个线程执行任务
            Thread th1 = new Thread(() =>
              {
                  while (!isStop)
                  {
                      Thread.Sleep(1000);
                      Console.WriteLine($"第{++index}次执行,线程运行中...");
                  }
              });
            th1.Start();
            //五秒后取消任务执行
            Thread.Sleep(5000);
            isStop = true;
            Console.ReadKey();
        }

 

Task中有一个专门的类 CancellationTokenSource 来取消任务执行,还是使用上边的例子,我们修改代码如下,程序运行的效果不变。

static void Main(string[] args)
        {
            CancellationTokenSource source = new CancellationTokenSource();
            int index = 0;
            //开启一个task执行任务
            Task task1 = new Task(() =>
              {
                  while (!source.IsCancellationRequested)
                  {
                      Thread.Sleep(1000);
                      Console.WriteLine($"第{++index}次执行,线程运行中...");
                  }
              });
            task1.Start();
            //五秒后取消任务执行
            Thread.Sleep(5000);
            //source.Cancel()方法请求取消任务,IsCancellationRequested会变成true
            source.Cancel();
            Console.ReadKey();
        }

 

CancellationTokenSource的功能不仅仅是取消任务执行,我们可以使用 source.CancelAfter(5000)实现5秒后自动取消任务,也可以通过 source.Token.Register(Action action)注册取消任务触发的回调函数,即任务被取消时注册的action会被执行。 看一个栗子:

 static void Main(string[] args)
        {
            CancellationTokenSource source = new CancellationTokenSource();
            //注册任务取消的事件
            source.Token.Register(() =>
            {
                Console.WriteLine("任务被取消后执行xx操作!");
            });

            int index = 0;
            //开启一个task执行任务
            Task task1 = new Task(() =>
              {
                  while (!source.IsCancellationRequested)
                  {
                      Thread.Sleep(1000);
                      Console.WriteLine($"第{++index}次执行,线程运行中...");
                  }
              });
            task1.Start();
            //延时取消,效果等同于Thread.Sleep(5000);source.Cancel();
            source.CancelAfter(5000);
            Console.ReadKey();
        }

执行结果如下,第5次执行在取消回调后打印,这是因为,执行取消的时候第5次任务已经通过了while()判断,任务已经执行中了:

 

最后看跨线程的栗子,点击按钮启动一个任务,给tetxtbox赋值,我们把Thread改成Task,代码如下:

public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void mySetValueBtn_Click(object sender, EventArgs e)
        {
            Task.Run(() =>
            {
                Action<int> setValue = (i) => { myTxtbox.Text = i.ToString(); };
                for (int i = 0; i < 1000000; i++)
                {
                    myTxtbox.Invoke(setValue,i);
                }
            });
        }
    }

 

运行界面如下,赋值的task不会阻塞UI线程:

 

 

异步方法(async/await)

在C#5.0中出现的async和await ,让异步编程变得更简单。我们看一个获取文件内容的栗子:

 class Program
    {
        static void Main(string[] args)
        {
            string content = GetContentAsync(Environment.CurrentDirectory + @"/test.txt").Result;
            //调用同步方法
            //string content = GetContent(Environment.CurrentDirectory + @"/test.txt");
            Console.WriteLine(content);
            Console.ReadKey();
        }
        //异步读取文件内容
        async static Task<string> GetContentAsync(string filename)
        {
            
            FileStream fs = new FileStream(filename, FileMode.Open);
            var bytes = new byte[fs.Length];
            //ReadAync方法异步读取内容,不阻塞线程
            Console.WriteLine("开始读取文件");
            int len = await fs.ReadAsync(bytes, 0, bytes.Length);
            string result = Encoding.UTF8.GetString(bytes);
            return result;
        }
        //同步读取文件内容
        static string GetContent(string filename)
        {
            FileStream fs = new FileStream(filename, FileMode.Open);
            var bytes = new byte[fs.Length];
            //Read方法同步读取内容,阻塞线程
            int len =  fs.Read(bytes, 0, bytes.Length);
            string result = Encoding.UTF8.GetString(bytes);
            return result;
        }
    }

test.txt内容是【hello world!】执行结果为:

 

上边的栗子也写出了同步读取的方式,将main函数中的注释去掉即可同步读取文件内容。我们可以看到异步读取代码和同步读取代码基本一致。async/await让异步编码变得更简单,我们可以像写同步代码一样去写异步代码。注意一个小问题:异步方法中方法签名返回值为Task,代码中的返回值为T。上边栗子中GetContentAsync的签名返回值为Task,而代码中返回值为string。牢记这一细节对我们分析异步代码很有帮助。

异步方法签名的返回值有以下三种:

① Task:如果调用方法想通过调用异步方法获取一个T类型的返回值,那么签名必须为Task;

② Task:如果调用方法不想通过异步方法获取一个值,仅仅想追踪异步方法的执行状态,那么我们可以设置异步方法签名的返回值为Task;

③ void:如果调用方法仅仅只是调用一下异步方法,不和异步方法做其他交互,我们可以设置异步方法签名的返回值为void,这种形式也叫做“调用并忘记”。

标签:基本,Task,Console,Thread,用法,线程,WriteLine,执行
From: https://www.cnblogs.com/davisdabing/p/17809805.html

相关文章

  • Shell的基本操作和编程入门
    操作:1)给变量赋值,练习echo命令,做下面这个题目:安装中文输入环境:http://rpm.pbone.net  选择第二个,点击右键,复制地址: 按顺序输入下面的命令:     安装完成后,输入zhcon,进入中文输入环境 a)把自己的名字赋值给变量name,把"是"赋值给变量is,把自己的班级名称......
  • PyQt5-16 屏幕坐标系的了解和基本使用
    (16什么是屏幕坐标系?)1什么是屏幕坐标系?2相关概念屏幕坐标系,即窗口相对于屏幕的坐标。屏幕左上角坐标称为原点坐标(0,0);窗口的坐标,即窗口的左上角相对原来的坐标,如下图示:窗口的宽和高也有两种,一种是工作取的高度,一种菜单栏的高度,比如如下说明:3代码实现创建一个窗口,......
  • 01_Vue 基本语法
    [tocc]Vue.js基本语法一、Vue基础Vue官网英文官网:https://vuejs.org/中文官网:https://cn.vuejs.orgVue概述Vue(读音/vjuː/,类似于view)是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue被设计为可以自底向上逐层应用。Vue的核心库只关注视图......
  • 2.Sklearn库标准数据集及基本功能
    1.sklearn数据集波士顿房价数据集波士顿房价数据集包含506组数据,每条数据包含房屋以及房屋周围的详细信息。其中包括城镇犯罪率、一氧化氮浓度、住宅平均房间数、到中心区域的加权距离以及自住房平均房价等。因此,波士顿房价数据集能够应用到回归问题上。使用sklearn.dataset......
  • 【小沐学Python】Python实现Web图表功能(Dash之基本功能)
    1、简介Dash是下载量最大,最值得信赖的Python框架,用于构建ML和数据科学Web应用程序。Dash是一个用来创建web应用的python库,它建立在Plotly.js(同一个团队开发)、React和Flask之上,主要的用户群体是数据分析者、AI从业者,可以帮助他们快速搭建非常美观的网页应用,而且不需要......
  • Vue-I8n国际化的基本使用
    安装npminstallvue-i18n@9或yarnaddvue-i18n@9初始化国际化信息为了方便管理我们新建一个lang文件夹作为专门的国际化专用,并在lang文件夹下新建zh跟en两个文件夹。//./lang/zh/index.tsexportdefault{statusbar:{uncommit:'未提交',unpus......
  • Docker关闭和使用以及针对镜像的基本操作
     本节针对docker简述一些基本操作,前提是docker正确安装 cmd下查看docker的常用命令:1、wsl 启动2、exit 退出3、wsl-l-v 查看docker 运行状态4、wsl--shutdown 关闭dokcer基本操作命令:1、启动docker2、dockerimages:查看本地镜像3、dockerpull拉取某个镜像,比......
  • 基本的Dos命令学习
    打开CMD的方式(Dos)1.win键+R输入“cmd”打开控制台常用的Dos命令1.#盘符切换输入"d:"2.#查看目录下的所有文件输入"dir"3.#切换目录"cd"(changedirectory)4.#切换上一级"cd.."5.#清除屏幕"cls"(clearscreen)6.#退出终端"exit"7.#查看电脑ip"ipconfig&q......
  • Java中set详细用法
    在Java中,Set是一种集合类型,它用于存储不重复的元素。Set接口继承自Collection接口,它的实现类包括HashSet、LinkedHashSet和TreeSet。下面是Set的一些常用用法:1.创建Set对象:Set<String>set=newHashSet<>();//创建HashSet对象Set<Integer>set=newLinkedHashSet<>();//......
  • C++中string类基本使用的详细归纳
    目录:string类的初始化操作实例化得到一个string类对象之后的常用成员函数的操作2.1从外部键盘获取输入的方式(注意与C风格字符串做区别)2.2比较string对象2.3遍历每个字符2.4string类中的insert()增加成员函数2.5string类中的erase()删除成员函数2.6常用基本操作......