欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Task任务简单了解及其的线程等待和延续、Await/Async关键字简述

程序员文章站 2022-06-02 11:47:29
...

什么是Task?

Task是用来实现多线程的类,在以前当版本中已经有了ThreadThreadPool,为什么还要提出Task类呢,这是因为直接操作Thread及ThreadPool,向线程中传递参数,获取线程的返回值及线程当启停时都非常的麻烦,所以微软的工程师对Thread进行了再封装,这就是Task,可以这么说Task是架构在Thread之上的,所以多线程时Task是我们的首选。

任务的线程是后台线程

本质上讲,任务就是一个线程池的增强版API

线程池的缺点

1、线程池获取结果并不方便

2、异步处理不方便

3、连续的异步操作不方便

目的:

场景1:  3个并发请求,通过3个接口获取数据,我们希望得到所有接口的数据进行下一步业务处理。

场景2:  3个并发请求,只要其中某一个完成了,就触发一个事件结果。

Task的声明

Task的声明有两种方式:

a.通过new 的方式来声明

 Task obj = new Task();

b.通过Task.Factory.StartNew的方式来声明

Task.Factory.StartNew(MyMethod);

这两种声明方式的区别:  第一种声明方式开启线程必须使用obj.Start(), 而通过Task.Factory.StartNew的方式则不用。

任务的创建

1.构造函数创建

static void Main(string[] args)
{
    Task task = new Task(() =>
    {
        // True 线程池线程
        Console.WriteLine(Thread.CurrentThread.IsThreadPoolThread);
    });
    task.Start();
    Console.ReadLine();
}

2.Task.Factory.StartNew创建

注意:任务工厂模式下,任务会立即调用执行。

static void Main(string[] args)
{
    Task.Factory.StartNew(() =>
    {
        // True 线程池线程
        Console.WriteLine(Thread.CurrentThread.IsThreadPoolThread);
    });
    Console.ReadLine();
}

3.不使用线程池设置来创建

static void Main(string[] args)
{
    Task task = new Task(() =>
    {
        // False 不使用线程池
        Console.WriteLine(Thread.CurrentThread.IsThreadPoolThread);
    }, TaskCreationOptions.LongRunning);
    task.Start();
    Console.ReadLine();
}

获取任务返回值

Task<string> task = new Task<string>(() =>
{
    DoSomething();
    return "此处为执行完成的结果";
});
task.Start();
Console.WriteLine(task.Result);

任务的回调

task.ContinueWith

(1)支持1个 task 接多个ContinueWith ;

(2)ContinueWith返回下一代 task,下一代可以继续ContinueWith;

Task<string> task = new Task<string>(() =>
{
     Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
     return "boy,";
});
task.ContinueWith((t) =>
{
     Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
     Console.WriteLine(t.Result + "come on.");
});
task.Start();
Console.ReadLine();

Task的线程等待和延续

Task下的线程等待和延续主要有以下几类:

Wait

针对单个Task的实例,可以让某一个线程进入等待。

WaitAny

执行的所有线程中只要有一个线程结束,主线程就执行,否则就卡住。

WatiAll

表示任务列表中所有的任务都已经结束后,主线程恢复。

WhenAny

表示任务列表中任何一个任务结束后,配合ContinueWith进行回调触发。

WhenAll

表示任务列表中所有任务都结束后,配合ContinueWith进行回调触发。

注意:WhenAny和WhenAll,都是开启一个新线程执行,不卡顿主线程。

TaskFactory

TaskFactory可以开启线程,当然也对应线程的等待和延续。

ContinueWhenAny:等价于Task的WhenAny+ContinueWith

ContinueWhenAll:等价于Task的WhenAll+ContinueWith

实例代码:

//1.ContinueWhenAny
Task.Factory.ContinueWhenAny(taskList.ToArray(),m=>{
	Console.WriteLine("")
})

//2.ContinueWhenAll
Task.Factory.ContinueWhenAll(taskList.ToArray(),m=>{
	Console.WriteLine("")
})

完整代码演示,创建控制台:

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

namespace ConsoleApp1
{
    class Program
    {
        public static void Main(string[] args)
        {
            List<Task<string>> taskList = new List<Task<string>>();
            Task<string> t1 = new Task<string>(() =>
            {
                return GetName();
            });
            Task<string> t2 = new Task<string>(() =>
            {
                return GetMongo();
            });
            taskList.Add(t1);
            taskList.Add(t2);
            t1.Start();
            t2.Start();            
            Task.WhenAll(taskList.ToArray()).ContinueWith(x =>
            {
                Console.WriteLine("任务执行完毕");
                Console.WriteLine(x.Result[0]);
                Console.WriteLine(x.Result[1]);
            });
            Console.ReadLine();
        }
        public static string GetName()
        {
            Thread.Sleep(5000);
            return "MSSQL";
        }
        public static string GetMongo()
        {
            Thread.Sleep(10000);
            return "MongoDB";
        }
    }
}

结果是:代码运行时间为10秒,依次输出相应内容。

await和async关键字

说到Task的返回值就不得不说await和async关键字了。当函数使用async标记后,返回值必须为void,Task,Task<T>,当返回值为Task<T>时,函数内部只需要返回T类型,编译器会自动包装成Task<T>类型。await关键字必须在具有async标记的函数内使用。

举个例子:

static void Main(string[] args)
{
     Console.WriteLine("调用主线程");
     Task<string> s = Testasync();
     Console.WriteLine(s.Result);
     Console.ReadKey();
 
}
static async Task<string> Testasync()
{
      Console.WriteLine("运行Task之前" + Thread.CurrentThread.ManagedThreadId);
 
      Task<string> t= Task.Run<string>(() =>
      {
          Console.WriteLine("运行Task" + Thread.CurrentThread.ManagedThreadId);
          Thread.Sleep();
          return "我是测试线程";
      });
      Console.WriteLine("运行Task之后" + Thread.CurrentThread.ManagedThreadId);
      var result = await t;
      return result;
}

async/await是C#5.0引入的一个关键词,其目的是让异步编程边的更加简洁,也是为了顺应异步编程的大趋势

功能

1.使用async/await能简单的创建异步方法,异步方法可以防止耗时操作阻塞当前线程

2.使用async/await来构建异步方法,更加简洁

语法

① 方法使用async作为修饰符

② 方法内部包含一个或者多个await表达式,表示可以异步完成的任务

③ 必须具备以下三种返回类型 void 、Task 、Task<T> ,其中后两种的返回对象标识未来完成的工作,调用方法和异步方法可以继续执行。

④异步方法的参数可以任意类型,但是不能为out和ref参数

⑤约定俗成,一般异步方法都是以 Async作为后缀的。

⑥ 除了方法之外,Lambda表达式和匿名函数也可以作为异步对象。

如果在async中没有出现await,那么该方法就会变成一个同步方法。

异步方法的控制流

1首先第一个await之前的部分,这部分应该是少量且无需长时间等待的代码。

2await表达式,表示需要被异步执行的任务,await可以有多个。

3在await表达式之后出现的方法的其余代码。

举例:

例子1:

public static async Task DoSomethingAsync()
{
    Console.WriteLine("异步方法同步部分1");
    long sum = 0;
    sum = await Task.Run(() =>
    {
        Console.WriteLine("异步执行部分1,线程ID:{0}",Thread.CurrentThread.ManagedThreadId);
        Thread.Sleep(2000);
        long result = 0;
        for (var i = 0; i < 1000000000; i++)
        {
            result += i;
        }
        Console.WriteLine("异步执行部分结束");
        return result;
    });
    Console.WriteLine("计算结果{0}", sum);
}

例子2:

public static async void DoSomethingAsync()
 {
     Task t1 = new Task(()=> {

         Console.WriteLine("异步开始了,线程:{0}", Thread.CurrentThread.ManagedThreadId);
         long result = 0;
         for (int i = 0; i < 1000000000; i++)
         {
             result += i;
         }
         Console.WriteLine("异步结束,计算结果{0},线程{1}", result, Thread.CurrentThread.ManagedThreadId);

     });
     t1.Start();
     await t1;


 }

async/await和普通异步的对比

思考:实现一个程序,每次任务休眠5秒后,返回100,打印100

 public static void DoSomething()
 {
     Console.WriteLine("异步方法开始1");
     var aaa= await Task.Run(() =>
     {
         Thread.Sleep(5000);
         return 100;
     });
 	Console.WriteLine("计算结果{0}",  aaa.result);
 }

调用:

static void Main(string[] args)
 {
    DoSomething();
    DoSomething();
    DoSomething();
 }

解释:上述代码运行后,是同步执行的,虽然Task会异步产生一个线程运行,但是由于Task中的返回值需要使用aaa.result获取,阻塞了线程,所以运行结果是同步的

将上述代码改装为异步

public  static void DoSomething111()
 {
     Console.WriteLine("异步方法开始1");
     //通过第一个异步方法得到结果
     var aaa= Task.Run(() =>
     {
           Thread.Sleep(5000);
           return 100;
     });
     aaa.ContinueWith(x =>
     {
           Console.WriteLine(x.Result);
     });
 }

解释:由于Task.Run()返回的是一个Task对象,所以如果不希望阻塞线程,那么可以调用ContinueWith进行回调处理,在回调中获取Task的返回值,ContinueWith不会阻塞主线程

 

优雅的写法async/await:

public async  static void DoSomething111()
 {
     Console.WriteLine("异步方法开始1");
     //通过第一个异步方法得到结果
     var aaa= await Task.Run(() =>
     {
           Thread.Sleep(5000);
           return 100;
     });
     Console.WriteLine("结果是{0}", aaa);
 }

解释:Task.Run()执行的是一个耗时任务,await标记表示等待任务结果,将结果赋值给aaa,没错,就是这么优雅。