【小白专用24.6.8】C# 异步任务Task和异步方法async/await详解

一、什么是异步

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

异步的好处在于非阻塞(调用线程不会暂停执行去等待子线程完成),因此我们把一些不需要立即使用结果、较耗时的任务设为异步执行,可以提高程序的运行效率。net4.0在ThreadPool的基础上推出了Task类,微软极力推荐使用Task来执行异步任务,现在C#类库中的异步方法基本都用到了Task;net5.0推出了async/await,让异步编程更为方便。本篇主要介绍Task、async/await相关的内容,其他异步操作的方式会在下一篇介绍。

二、Task介绍

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

1、Task创建和运行

首先看一下怎么去创建并运行一个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不会阻塞主线程。上边的例子Task都没有返回值,我们也可以创建有返回值的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.Resut获取结果时会阻塞Task线程(Task的执行都是异步的,获取一个线程执行该方法内部的业务),不会阻塞主线程,即如果task没有执行完成,会等待task执行完成获取到Result,然后再执行后边的代码,程序运行结果如下:

有些场景下我们想让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();

}

执行结果如下:

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

实现阻塞主线程,task.Wait() 表示等待task执行完毕; Task.WaitAll(Task[] tasks) 表示只有所有的task都执行完成了再解除阻塞; Task.WaitAny(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都执行完毕再执行主线程
            //执行【task1.Wait();task2.Wait();】可以实现相同功能
            Task.WaitAll(new Task[] { task1, task2 });
            Console.WriteLine("主线程执行完毕!");
            Console.ReadKey();

        }

执行结果如下:

如果将栗子中的WaitAll换成WaitAny,那么任一task执行完毕就会解除线程阻塞,执行结果是:

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

上边的Wait/WaitAny/WaitAll方法返回值为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();

 }

4、Task的任务取消(CancellationTokenSource)

1.Task取消任务执行

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,代码如下:

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

}

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

三、异步方法(async/await)

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

        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内容是【正在学习中】执行结果为:

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

异步方法签名的返回值有以下三种:
① Task:如果调用方法想通过调用异步方法获取一个T类型的返回值,那么签名必须为Task
② Task:如果调用方法不想通过异步方法获取一个值,仅仅想追踪异步方法的执行状态,那么我们可以设置异步方法签名的返回值为Task;
③ void:如果调用方法仅仅只是调用一下异步方法,不和异步方法做其他交互,我们可以设置异步方法签名的返回值为void,这种形式也叫做“调用并忘记”。

小结:到这里Task,async/await的简单使用已经基本结束了,一些高级特性等到工作遇到了再去研究。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/691187.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

springboot+minio+kkfileview实现文件的在线预览

在原来的文章中已经讲述过springbootminio的开发过程&#xff0c;这里不做讲述。 原文章地址&#xff1a; https://blog.csdn.net/qq_39990869/article/details/131598884?spm1001.2014.3001.5501 如果你的项目只是需要在线预览图片或者视频那么可以使用minio自己的预览地址进…

C++期末复习总结(2)

目录 1.运算符重载 2.四种运算符重载 &#xff08;1&#xff09;关系运算符的重载 &#xff08;2&#xff09; 左移运算符的重载 &#xff08;3&#xff09;下标运算符的重载 &#xff08;4&#xff09;赋值运算符的重载 3.继承的方式 4.继承的对象模型 5.基类的构造 6…

中缀表达式和前缀后缀

在中缀表达式中&#xff0c;操作数可能与两个操作符相结合 但是&#xff0c;想要不带括号无歧义&#xff0c;且不需要考虑运算符优先级和结合性 所以考虑 前缀表达式&#xff0c;波兰表达式 后缀表达式 逆波兰表达式 对于人来说&#xff0c;中缀表达式是最容易读懂的。但是对于…

C语言之字符函数总结(全部!),一篇记住所有的字符函数

前言 还在担心关于字符的库函数记不住吗&#xff1f;不用担心&#xff0c;这篇文章将为你全面整理所有的字符函数的用法。不用记忆&#xff0c;一次看完&#xff0c;随查随用。用多了自然就记住了 字符分类函数和字符转换函数 C语言中有一系列的函数是专门做字符分类和字符转换…

无人机、机器人10公里WiFi远距离图传模块,实时高清视频传输,飞睿CV5200模组方案,支持mesh自组网模块

在快速发展的物联网时代&#xff0c;远距离无线通信技术已成为连接各种智能设备的关键。无人机、安防监控、机器人等领域对数据传输的距离和速度要求越来越高。 公里级远距离WiFi模组方案可以通过多种技术和策略的结合来实现无人机和机器人之间的高效通信传输。 飞睿智能CV52…

【Java毕业设计】基于JavaWeb企业违规信息综合管理系统

文章目录 摘 要ABSTRACT目 录1 概述1.1 研究背景及意义1.2 国内外研究现状1.3 拟研究内容1.4 系统开发技术1.4.1 Java编程语言1.4.2 SpringBoot框架1.4.3 MySQL数据库1.4.4 B/S结构1.4.5 MVC模式 2 系统需求分析2.1 可行性分析2.2 功能需求分析 3 系统设计3.1 功能模块设计3.2 …

Python应用开发——30天学习Streamlit Python包进行APP的构建(5)

上几次我们已经将一些必备的内容进行了快速的梳理,让我们掌握了streanlit的凯快速上手,接下来我们将其它的一些基础函数再做简单的梳理,以顺便回顾我们未来可能用到的更丰富的函数来实现应用的制作。 st.write_stream 将生成器、迭代器或类似流的序列串流到应用程序中。 …

数据总线、位扩展、字长

数据总线&#xff08;Data Bus&#xff09; 定义 数据总线是计算机系统中的一组并行信号线&#xff0c;用于在计算机内部传输数据。这些数据可以在中央处理器&#xff08;CPU&#xff09;、内存和输入/输出设备之间传输。 作用 数据传输&#xff1a;数据总线负责在计算机各…

Elasticsearch 认证模拟题 - 15

一、题目 原索引 task1 的字段 title 字段包含单词 The&#xff0c;查询 the 可以查出 1200 篇文档。重建 task1 索引为 task1_new&#xff0c;重建后的索引&#xff0c; title 字段查询 the 单词&#xff0c;不能匹配到任何文档。 PUT task1 {"mappings": {"…

Jar包部署为linux系统服务

文章目录 引言I 以系统服务的方式部署(推荐)1.1 创建systemd服务1.2 SSH上传jar包,并重启服务1.3 收集自定义systemd服务的日志【可选】II 脚本部署方式(不推荐)2.1 启动脚本2.2 关闭脚本2.3 SSH上传jar包,并重启服务III 打包3.1 build中的plugins中标签的含义3.2 jar中没…

代码随想录算法训练营第36期DAY50

DAY50 如果写累了就去写套磁信吧。 198打家劫舍 class Solution {public: int rob(vector<int>& nums) { vector<int> dp(nums.size()); dp[0]nums[0]; if(nums.size()1) return nums[0]; dp[1]max(nums[0],nums[1]); …

【Unity UGUI】Screen.safeArea获取异形屏数据失败

Screen.safeArea获取不到异形屏的尺寸位置等数据 检查AndroidManifest.xml文件是否有设置&#xff1a;android:theme"style/UnityThemeSelector"&#xff0c;没有加上即可 android:theme"style/UnityThemeSelector"

基于大模型 Gemma-7B 和 llama_index,轻松实现 NL2SQL

节前&#xff0c;我们星球组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学. 针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。 汇总合集&…

时光正好保剑锋的抱治百病与成年人的世界

《时光正好》&#xff1a;保剑锋的“抱治百病”与成年人的世界在繁忙的都市里&#xff0c;每个角落上演着各自的人生戏码。而在这些戏码中&#xff0c;由保剑锋主演的《时光正好》无疑成为了近期引人注目的焦点。这部电视剧以其真实而深刻的剧情&#xff0c;让我们看到了成年人…

用于认知负荷评估的集成时空深度聚类(ISTDC)

Integrated Spatio-Temporal Deep Clustering (ISTDC) for cognitive workload assessment 摘要&#xff1a; 本文提出了一种新型的集成时空深度聚类&#xff08;ISTDC&#xff09;模型&#xff0c;用于评估认知负荷。该模型首先利用深度表示学习&#xff08;DRL&#xff09;…

Debug-014-nginx代理路径的一条规则

直接上图&#xff1a; 今天看禹神的前端视频&#xff0c;讲到在nginx中代理路径的时候&#xff0c;有一个规则&#xff1a; 如果/dev和下面的proxy_pass路径最后都带‘/’,那么就是匹配到dev之后要删除dev,然后再带着后面的路径&#xff1b;如果/dev和下面的proxy_pass路径最后…

【上篇】从 YOLOv1 到 YOLOv8 的 YOLO 物体检测模型历史

YOLO 型号之所以闻名遐迩,主要有两个原因:其速度和准确性令人印象深刻,而且能够快速、可靠地检测图像中的物体。 在本文中,我将与大家分享我在阅读一篇长达 30 页的综合性论文时获得的见解,该论文深入探讨了 YOLO 模型的进步。 这篇评论全面概述了 YOLO 框架的演变过程,…

自然语言处理:第三十二章HippoRAG:性能提高20% - 受海马体启发的RAG

文章链接: HippoRAG: Neurobiologically Inspired Long-Term Memory for Large Language Models 项目地址: OSU-NLP-Group/HippoRAG: HippoRAG is a novel RAG framework inspired by human long-term memory that enables LLMs to continuously integrate knowledge across e…

How to: Add and Customize Toolbar Skin Selectors

You can add skin selectors to a toolbar (BarManager) and Ribbon Control to allow users to choose skins at runtime. 将皮肤选择器添加到工具栏 At design time, click the [Add] button in the toolbar, and select a skin selector from the Skin Item sub-menu. 以下…

【C++课程学习】:C++入门(引用)

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;C课程学习 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 &#x1f369;1.引用的概念&#xff1a; &#x1f369;2.引用和指针是两个概念&#xff1a; &#x…