Task与async/await(进阶篇&小白易懂版)

Task和asyncawait详解

Task

本篇为Task进阶篇,Task的基础在专栏往期C#博客有具体讲解,可以关注追更。

传送门link
在这里插入图片描述

什么是异步和同步

C#中同步异步是两种不同的编程模式,用于控制代码的执行方式。

同步模式是指代码按照从上到下的顺序依次执行,在执行一个任务时会一直阻塞在那里等待其完成。

而异步模式则是指在调用一个任务后立即返回,不会等待这个任务完成,而是通过回调机制在任务完成时通知调用者,可以同时执行多个任务,提高程序的效率。

使用场景:当你有一些可能会阻塞线程(如IO操作、网络请求、复杂计算等)的操作时,可以将它们封装为任务,并使用Task类来异步执行它们。这样可以避免阻塞UI线程或其他重要线程,提高应用程序的响应性和性能。此外,通过使用上述的方法,你可以轻松地控制任务的执行情况。

Task的创建与运行

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

关键点提取

  1. 某种程度上说,一个task类似于一个thread(线程)或者ThreadPool(线程池)中的工作项(线程),但是Task处于一个更高的抽象上(封装了线程的操作)
  2. Task对象记录了该任务的状态,运行结果 任务的操作方法等信息
    Status属性为任务的当前状态
  3. Task四种状态
    Created 任务创建完成
    WaitingToRun 已经启动 等待执行
    Runing 正在执行
    RunToComletion 执行完成
    在这里插入图片描述
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;

namespace _06_Task
{
    public partial class Form1 : Form
    {
        //Task 任务
        //某种程度上说,一个task类似于一个thread(线程)或者ThreadPool(线程池)中的工作项(线程),但是Task处于一个更高的抽象上(封装了线程的操作)
        public Form1()
        {
            InitializeComponent();
        }
        Task tk;
        private void button1_Click(object sender, EventArgs e)
        {
            //创建任务
            tk = new Task(Fn);
            //启动任务
            tk.Start();

            //Task对象记录了该任务的状态,运行结果 任务的操作方法等信息
            //Status属性为任务的当前状态

            Task tk1 = Task.Run(Fn);//创建并启动
            
            Console.WriteLine(tk.Status);
          

        }
        void Fn()
        {
            Console.WriteLine(tk.Status);
            Thread.Sleep(2000);
            Invoke(new Action(() => {
               label1.Text = "老坛酸菜踩好了";
            }));
        }
    }
}

代码解释:

在这个示例中,有一个方法被封装为任务(Task)并异步执行。

  1. Fn():这个方法模拟了一个耗时操作。它让当前线程休眠2秒,然后更新UI元素label1的文本。

button1_Click事件处理器中,首先创建了一个新的任务tk,并异步执行Fn()方法。然后,打印出任务的状态。注意,任务的状态可以通过Task.Status属性获取,它返回一个TaskStatus枚举值,表示任务的当前状态。

Task的控制

使用场景

关键点提取

  • Task.Wait():阻塞当前线程直到任务完成。
  • Task.WaitAll(t1, t2):阻塞当前线程直到所有任务都完成。
  • Task.WaitAny(t1, t2):阻塞当前线程直到任何一个任务完成。
  • t1.ContinueWith(t => ...):注册一个回调函数,该函数会在t1任务完成时执行。
  • TaskFactory.ContinueWhenAll(new Task[] { t1, t2 }, t => ...):注册一个回调函数,该函数会在所有任务都完成时执行。
  • TaskFactory.ContinueWhenAny(new Task[] { t1, t2 }, t => ...):注册一个回调函数,该函数会在任何一个任务完成时执行。
    在这里插入图片描述
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace _07_Task的控制
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        void Fn1()
        {
            Thread.Sleep(1000);
            Console.WriteLine("Fn1方法执行完成");
        } void Fn2()
        {
            Thread.Sleep(1000);
            Console.WriteLine("Fn2方法执行完成");
        }
        private void button1_Click(object sender, EventArgs e)
        {
            //Task和线程相比的优势可以灵活控制
            //1.task.Wait()等待任务完成(一般用于在任务执行中等待另一个任务)
            //Task t1 = Task.Run(Fn1);
            //t1.Wait();
            将会卡住当前(主)线程,直到任务执行完毕
            //Console.WriteLine("任务执行完毕");


            //2.Task.WaitAll 等待所有任务执行完成,再执行(主)线程
            //Task t1 = Task.Run(Fn1);
           
            //Task t2 = Task.Run(Fn2);
            //Task.WaitAll(t1,t2);
            //Console.WriteLine("所有任务执行完毕,主线程执行");



            //3..Task.WaitAny 等待某个任务执行完成,再执行(主)线程
            Task t1 = Task.Run(Fn1);

            Task t2 = Task.Run(Fn2);
            Task.WaitAll(t1, t2);
            Console.WriteLine("t1或t2其中一个任务执行完毕,主线程执行");



            //4.不阻塞的等待(一个任务)
            t1.ContinueWith((t) =>
            {
                Console.WriteLine("t1任务完成了,可以执行其他任务了");
            });

            //5.不阻塞的等待(多个任务)
            //并且等待所有任务完成
            TaskFactory factory = new TaskFactory();
            factory.ContinueWhenAll(new Task[] { t1, t2 }, t =>
            {
                Console.WriteLine("所有任务执行完毕,主线程执行");
            });
            //并且等待其中一个任务完成
            TaskFactory factory1 = new TaskFactory();
            factory1.ContinueWhenAny(new Task[] { t1, t2 }, t =>
            {
                Console.WriteLine("t1或t2执行完毕,主线程执行");
            });
        }
    }
}

代码解释

在这个示例中,有两个方法被封装为任务(Task)并异步执行。

  1. Fn1()Fn2():这两个方法模拟了两个耗时操作。它们分别让当前线程休眠1秒,然后在控制台打印一条消息。

button1_Click事件处理器中,首先创建了两个新的任务t1t2,分别异步执行Fn1()Fn2()方法。然后,展示了如何使用Task.Wait(), Task.WaitAll(), Task.WaitAny(), Task.ContinueWith(), TaskFactory.ContinueWhenAll(), 和 TaskFactory.ContinueWhenAny()方法来控制任务的执行。

带有返回值的Task

使用场景:当Task运行的方法有返回值

关键点提取

  1. 当我们创建任务的时候,传递的是一个有返回值的方法,就会得到一个泛型任务,Task<方法的返回值类型>

  2. 直接使用task.Result获取任务的结果时.会自动等待任务完成(会隐式调用wait方法)

    在这里插入图片描述

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace _08带有返回值的Task
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        int Fn()
        {
            int sum = 0;
            for (int i = 0; i < 100; i++)
            {
                Thread.Sleep(2);
                sum += i;
            }
            return sum;
        }
        string MrWang()
        {
            Thread.Sleep(2000);
            Console.WriteLine("王先生下班了");
            return "王先生";
        }
        string MissLi()
        {
            Thread.Sleep(4000);
            Console.WriteLine("李小姐下班了");
            return "李小姐";
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //启动任务并进行计算
            //当我们创建任务的时候,传递的是一个有返回值的方法,就会得到一个泛型任务,Task<方法的返回值类型>
            Task<int> task = Task.Run(Fn);

            //task.Result获取任务的执行结果  当直接使用task.Result获取任务的结果时.会自动等待任务完成(会隐式调用wait方法)
            Console.WriteLine(task.Result);


            //当需要获取结果时不阻塞其他线程进行,可以使用ContinueWith
            task.ContinueWith(t =>
            {
                Console.WriteLine(task.Result);

            });


            Task<string> t2=Task.Run(MrWang);
            Task<string> t3=Task.Run(MissLi);

            //要同时获取t2和t3的结果,可以使用上节TaskFactory 类的ContinueWhenAll
            //比如王先生和李小姐都下班了,执行一起吃饭
            TaskFactory tf= new TaskFactory();
            tf.ContinueWhenAll(new Task<string>[] { t2, t3 }, t =>
            {
                Console.WriteLine("都下班了,一起吃饭");
            });


            //要只获取t2和t3的其中一个结果,可以使用上节TaskFactory 类的ContinueWhenAny方法
            //比如王先生和李小姐谁先下班了,谁回家做饭
            tf.ContinueWhenAny(new Task<string>[] { t2, t3 }, t =>
            {
                Console.WriteLine($"{t.Result}先下班了,回家做饭");
            });
        }
    }
}

代码解释

在这个示例中,有三个方法被封装为任务(Task<T>)并异步执行。

  1. Fn():这个方法计算0到99的和,并返回结果。它被封装在Task.Run(Fn)中以在一个新的线程上运行,并返回一个Task<int>对象。
  2. MrWang()MissLi():这两个方法模拟了两个人下班的情况。它们分别让当前线程休眠2秒和4秒,然后在控制台打印一条消息,并返回一个字符串。它们也被封装在Task.Run中以在新的线程上运行,并返回Task<string>对象。

button1_Click事件处理器中,首先使用Task<int> task = Task.Run(Fn)来异步执行Fn()方法,并返回一个Task<int>对象。然后,使用Console.WriteLine(task.Result)来获取并打印任务的结果。注意,直接访问task.Result会阻塞当前线程直到任务完成。为了避免阻塞,可以使用task.ContinueWith(t => Console.WriteLine(task.Result))来注册一个回调函数,该函数会在任务完成时执行,并打印出结果。

接着,创建了两个新的任务t2t3,分别异步执行MrWang()MissLi()方法。然后,使用TaskFactory.ContinueWhenAll方法来注册一个回调函数,该函数会在t2t3都完成时执行,并打印出一条消息。最后,使用TaskFactory.ContinueWhenAny方法来注册另一个回调函数,该函数会在t2t3中任何一个完成时执行,并打印出一条消息。

async/await

使用场景:当你有一些可能会阻塞线程(如IO操作、网络请求、复杂计算等)的操作时,可以将它们封装为可等待的方法,然后使用async/await关键字来异步执行它们。这样可以避免阻塞UI线程或其他重要线程,提高应用程序的响应性和性能。

关键点提取

  • 使用await 关键字等待一个任务执行完成,如果该任务有返回值,则可以使用变量接收
    注意:await 关键字 只能使用在async修饰的函数中
    在这里插入图片描述
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace _09_async_await
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        string MrWang()
        {
            Thread.Sleep(1000);
            return "王先生";
        } void MissLi()
        {
            Thread.Sleep(5000);
            Console.WriteLine("王小姐下班了");
        }

        private  void button1_Click(object sender, EventArgs e)
        {
            Task<string> t1 = Task.Run(MrWang);
            t1.ContinueWith(t =>
            {
                Console.WriteLine(t.Result + "下班了");

            });
        }

        private async void button2_Click(object sender, EventArgs e)
        {
            //简写为:
            //使用await 关键字等待一个任务执行完成,如果该任务有返回值,则可以使用变量接收
            //注意:await 关键字 只能使用在async修饰的函数中
            string s = await Task<string>.Run(MrWang);
            await Console.Out.WriteLineAsync(s + "下班了");


            //如果没有返回值 直接await即可
            await Task<string>.Run(MissLi);
        }
    }
}

代码解释

在这个示例中,有两个方法被封装为任务(TaskTask<T>)并异步执行。

  1. MrWang():这个方法会让当前线程休眠1秒钟,然后返回字符串"王先生"。
  2. MissLi():这个方法会让当前线程休眠5秒钟,然后在控制台打印"王小姐下班了"。

button1_Click事件处理器中,首先使用Task<string> t1 = Task.Run(MrWang)来异步执行MrWang()方法,并返回一个Task<string>对象。然后,使用t1.ContinueWith(t => Console.WriteLine(t.Result + "下班了"))来注册一个回调函数,该函数会在t1任务完成时执行,并打印出结果。

button2_Click事件处理器中,首先使用string s = await Task<string>.Run(MrWang)来异步执行MrWang()方法,并等待它完成并获取结果。然后,使用await Console.Out.WriteLineAsync(s + "下班了")来异步打印结果。最后,使用await Task<string>.Run(MissLi)来异步执行MissLi()方法,并等待它完成。

封装可等待方法

使用场景:当你有一些可能会阻塞线程(如IO操作、网络请求、复杂计算等)的操作时,可以将它们封装为可等待的方法,然后使用async/await关键字来异步执行它们。这样可以避免阻塞UI线程或其他重要线程,提高应用程序的响应性和性能。

关键点提取

  1. await Console.Out.WriteLineAsync(item);
    等同于Console.WriteLine();
  2. await 关键字 只能等待一个Task
    在这里插入图片描述
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace _10_封装可等待的方法
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        void Fn()
        {
            Thread.Sleep(1000);
            Console.WriteLine("结束");

        }
        //如果我们需要让我们的方法可以被等待,只需要让我们的方法返回一个Task即可
        Task<string[]> ReadDir()
        {
            Task<string[]> t = Task.Run(() =>
            {
                Thread.Sleep(2000);
                return new string[] { "目录1","目录2" };
            });
            return t;
        }
        private async void button1_Click(object sender, EventArgs e)
        {
            //await 关键字 只能等待一个Task
            await Task.Run(Fn);

            string[]s= await ReadDir();
            foreach(var item in s)
            {
               
                await Console.Out.WriteLineAsync(item);
                //等同于Console.WriteLine();
            }

        }

        
    }
}

代码解释

这段代码是一个Windows Forms应用程序,其中使用了C#的async/await关键字来实现异步编程。在这个示例中,有两个方法被封装为可等待(返回TaskTask<T>)的方法。

  1. Fn():这个方法会让当前线程休眠1秒钟,然后在控制台打印"结束"。它被封装在Task.Run(Fn)中以在一个新的线程上运行,并返回一个Task对象,这样就可以使用await关键字等待它完成。
  2. ReadDir():这个方法模拟了读取目录的操作。它创建并返回一个Task<string[]>对象,该任务会在新的线程上运行,休眠2秒钟(模拟耗时的IO操作),然后返回一个字符串数组。因为它返回了一个Task<string[]>,所以可以使用await关键字等待它完成并获取结果。

button1_Click事件处理器中,首先使用await Task.Run(Fn)来异步执行Fn()方法,并等待它完成。然后,使用string[] s = await ReadDir()来异步执行ReadDir()方法,并等待它完成并获取结果。最后,遍历结果数组,并使用await Console.Out.WriteLineAsync(item)来异步打印每个元素
在这里插入图片描述

总结

Task和async/await是现代编程语言中用于简化异步编程的两个关键概念。它们在处理长时间运行的任务时非常有用,可以在等待结果的同时释放线程以处理其他工作,从而提高应用程序的响应性和性能。
Task和async/await协同工作,提供了一种强大的异步编程模型。Task负责执行后台操作,而async/await提供了一种优雅的编写异步代码的方式。使用async/await时,开发者可以编写出易于理解和维护的异步代码,同时保持程序的响应性和性能。

拓展(博客推荐)
传送门link
传送门link
传送门link

如果觉得文章还不错,可以点赞、收藏和转发,以支持作者继续创作更多教程。 另外本专栏将会持续更新,作者专栏中有已经更新完毕的C#基础教程!!!
————————————————

  • 42
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值