(16)线程的实例认识:Await,Async,ConfigureAwait

news2025/2/22 14:00:30


    继续(15)的例子

一、ConfigureAwait()的作用

        private async void BtnAsync_Click(object sender, EventArgs e)//异步
        {
            Stopwatch sw = Stopwatch.StartNew();
            TxtInfo.Clear();
            AppendLine("异步检索开始...");
            AppendLine($"当前线程Id:{Environment.CurrentManagedThreadId}");//b
            int idx = 0;
            foreach (var b in Data.Books)
            {
                string t = await Task.Run(b.Search).ConfigureAwait(false);//a
                AppendLineThread($"{++idx}.{t}--线程Id:{Environment.CurrentManagedThreadId}");//c
            }
            sw.Stop();
            AppendLineThread($"异步检索完成:{Convert.ToSingle(sw.ElapsedMilliseconds) / 1000}秒");
        }    


    
    1、上面a后面添加的ConfigureAwait(false)是什么意思?
        ConfigureAwait(true)和ConfigureAwait(false)也是用于配置async/await操作的,它们用于控制异步操作在await之后是否在原始的上下文中继续执行。
        当ConfigureAwait(true)时,异步操作在await之后会返回到原始的上下文中(一般是调用方线程或UI线程)继续执行。
        当ConfigureAwait(false)时,异步操作在await之后会在非原始的上下文中(一般指当前的异步线程)继续执行。
        
        理解例子(2):
        假设你是一个餐厅的经理,你需要安排服务员去执行一些任务。服务员是你的线程,而任务是异步操作。你可以选择两种不同的方式来安排服务员执行任务。
        当你使用ConfigureAwait(true)时,这就像你让服务员在原始的上下文中执行任务。这意味着服务员会在你所在的位置继续执行任务。例如,如果你在前台接待顾客,然后遇到了一个异步任务(比如接听电话),你可以选择让服务员在你身边继续执行任务,这样你接听完电话后可以让他立即继续处理顾客。
        而当你使用ConfigureAwait(false)时,这就像你让服务员离开原始的上下文去执行任务。这意味着服务员会离开你的位置去执行任务。例如,如果你在前台接待顾客,然后遇到了一个异步任务(比如处理支付),你可以选择让服务员"离开你的位置"去处理支付,这样你可以继续接待其他顾客。
        所以,ConfigureAwait(true)让异步操作在原始的上下文中继续执行,就像让服务员在你身边继续执行任务。而ConfigureAwait(false)让异步操作在非原始的上下文中继续执行,就像让服务员离开你的位置去执行任务。


    2、为true与false的好处?
        ConfigureAwait(true)的好处:
        保留当前的上下文环境:在某些情况下,你可能需要在异步操作执行完毕后回到原始的上下文环境,例如,你在UI线程上调用了一个异步操作,然后在操作完成后需要更新UI。使用ConfigureAwait(true)可以确保异步操作在原始的上下文中继续执行,由于要在异步操作中进行线程切换,所以有上下文恢复的开销。
        简化代码:如果你确信异步操作不会引发线程上下文相关的问题,并且想要保持在原始的上下文中执行,那么使用ConfigureAwait(true)可以简化代码,避免了显式指定ConfigureAwait(false)的需要。
        
        ConfigureAwait(false)的好处:
        提高性能:如果你的异步操作不需要回到原始的上下文环境,并且没有对UI或特定上下文的依赖,使用ConfigureAwait(false)可以在异步操作中避免不必要的线程切换和上下文恢复的开销,从而提高性能。
        避免死锁:在某些情况下,当异步操作依赖于特定的上下文环境时,使用ConfigureAwait(false)可以避免出现死锁的可能性。例如,在UI线程上使用ConfigureAwait(true)可能导致异步操作在等待UI线程资源时出现死锁,因为UI线程正在等待异步操作完成。
        总体来说,ConfigureAwait(true)适用于需要保留原始上下文环境的情况,可以避免线程切换和上下文恢复的开销,并简化代码。而ConfigureAwait(false)适用于不需要回到原始上下文环境的情况,可以提高性能并避免死锁。
        注意,使用ConfigureAwait(false)也意味着您要确保在异步操作中不使用与UI线程上下文相关的资源或数据。否则,可能会导致线程安全问题或其他错误。

    
    3、UI线程与异步线程可以是同一个线程吗?
        UI线程与异步线程并不是绝对的不一样,它们类似对象,可以同时指向同一个线程,比如UI线程可以指向UI线程本身,异步线程也可以在同时指向UI线程。
        因此,UI线程和异步线程可以同时指向同一个实际线程。
        UI线程和异步线程实际上是线程的角色或标识(变量名),用于区分它们在应用程序中的不同任务和行为。虽然它们可以在某些情况下指向同一个线程,但它们通常用于不同的目的和上下文。

        UI线程通常负责用户界面的呈现、响应用户输入以及处理UI事件。异步线程一般用于执行耗时的操作,以避免阻塞UI线程,以及在后台执行任务或处理并发操作。
        虽然可以出现UI线程和异步线程指向同一个线程的情况,但仍然需要考虑线程间的上下文切换和线程安全性。UI线程和异步线程在相应的上下文中进行任务处理,以确保正确的执行和交互。

        总之,UI线程和异步线程可以共享同一个线程对象,但它们在应用程序中具有不同的角色和任务。
    
    
    4、true与false的效果
        上例的task与上下文无关,所以用true或false都不会有多大的影响。但我们可以查看一下线程ID的变化:
        
        
        
        左边为true,异步线程y操作a处task.run后,根据true的设置,控制权就会将线程y交还线程池(让线程池进行管理y,是释放还是利用,都与现在的无关了),然后,控制权切换恢复到原始上下文(即UI线程),这时就是UI线程在执行了,以确保后续的代码在UI线程上执行。因此,b处是UI线程在执行,c处也是UI线程在执行(UI线程委托UI自己做事),因此,c处的线程ID与UI线程的线程ID相同。上面ID都为1。
        注意:
        在部分编程框架和操作系统中,UI线程的ID可能被预先分配为1。注意,这个结果是特定环境下的表现,并不适用于所有的编程框架和操作系统。在其他环境中,UI线程的ID可能有不同的分配规则或方式。因此,在编写代码时,最好避免依赖特定环境的线程ID分配方式,而是使用提供的API或方法来获取线程ID。
        
        右边为false,异步线程y操作在a处task.run后,根据false的设置,线程y不会交还给线程池,也不会尝试恢复到原始上下文(例如切换到UI线程),控制操作权仍然在线程y中紧紧把握,然后线程y就当家做主,继续执行b处下面的代码,这个异步线程y是由task.run时线程池智能分配的,所以每一个task.run对应一个异步线程,c也由这个异步线程在执行,所以c处因为线程池的分配而显示的异步线程ID是随机的,可能相同可能不同。所以在b是ID是1,在c处随机由线程池决定。
        当为false,在c后面如果操作UI控件,比如TxtInfo.AppendText="1111";将会出错。因为false后,返回的线程只能处理与UI无关的事,结果现在处理TxtInfo,将引发异常。上面代码能正常是因为后面全是委托AppendLintThread。

二、Await/Async


    1、例子界面
        
        


        
        


        
        代码:

        public Form1()
        {
            InitializeComponent();
        }

        private readonly StringBuilder strResult = new StringBuilder();

        private void Test_ConfigureAwait(object sender, EventArgs e)
        {
            Stopwatch sw = Stopwatch.StartNew();
            string s1 = cbAwait.Checked.ToString();
            string s2 = cbConfigureAwait.Checked.ToString();
            strResult.Clear();
            strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】主线程开始:Await:{s1},ConfigureAwait:{s2}");
            ChildMethod();
            strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】主线程开始等待");
            Thread.Sleep(3000);
            sw.Stop();
            strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】主线程结束{sw.ElapsedMilliseconds}ms");
            MessageBox.Show(Owner, "主线程结束,输出结果", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
            Print(strResult.ToString());
        }

        private async void ChildMethod()
        {
            strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】ChildMethod开始......");
            Stopwatch sw = Stopwatch.StartNew();
            if (cbAwait.Checked)
            {
                await Task.Run(() =>
                {
                    strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】子线程开始......");
                    Thread.Sleep(2000);
                    strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】子线程延时2000ms结束");
                }).ConfigureAwait(cbConfigureAwait.Checked);
            }
            else
            {
                Task.Run(() =>
                {
                    strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】子线程开始......");
                    Thread.Sleep(2000);
                    strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】子线程延时2000ms结束");
                }).ConfigureAwait(cbConfigureAwait.Checked);
            }
            sw.Stop();
            strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】ChildMethod结束{sw.ElapsedMilliseconds}ms");
        }

        private void Print(string s)
        {
            txtInfo.AppendText(s + $"{Environment.NewLine}");
            txtInfo.ScrollToCaret(); txtInfo.Refresh();
        }

        private void BtnPrint_Chick(object sender, EventArgs e)
        { Print(strResult.ToString()); }


        
    
    2、在C#中,使用`await`关键字可以实现异步执行,并且在等待异步结果返回时不阻塞当前线程,而是将控制权交还给调用方。通常情况下,这个调用方可以是UI线程,但也可以是其他线程。当调用方遇到`await`关键字时,它会暂停执行并允许其他代码继续执行,不会阻塞线程。

        在执行到`await`关键字时,异步操作将开始执行,并且调用方将继续执行该关键字后面的代码。当异步操作完成并返回结果时,调用方将恢复执行且可以处理异步操作的结果。无论调用方在`await`之前还是之后结束,都不会影响异步操作的执行。

        需要注意的是,当使用`await`时,调用方必须在某种异步上下文中,例如使用异步方法、异步事件处理程序或通过`Task.Run`等方法创建异步操作。这样才能正确地管理和调度异步操作,并使其在适当的时候恢复执行。

        总结起来,`await`关键字可以使代码在等待异步结果返回时不被阻塞,并将控制权交还给调用方,以便它可以继续执行其他代码。调用方可以是UI线程或其他线程,而执行的顺序将取决于异步操作的完成时间。
    
    
    3、上面什么都不选择时
        主方法先调用子方法,由于Task.Run是异步,所以子方法中一闪而过直接执行最下面的子方法结束信息,至于task.run让它自行2秒后添加信息,而这期间,主方法也是只要一调用子方法就不管它,也直接执行到延时3秒处,所以当子方法延时2秒,稍后主方法的延时3秒也到期了,后面就添加主方法结束的信息。
        
        当只选择Await时,
        主方法调用子方法后,也是自行继续向下执行。子方法遇到await Task.run就需要阻塞执行等待2秒后,因为configureawait没选中,为false,所以task.run后面的代码仍然由异步线程直接继续执行下去,直到子方法的信息追加完成,当然肯定比主方法的延时3秒更早地追加信息,所以最后显示的还是主方法结束的信息。
    
    
    
    3、再一次看一下ConfigureAwait的效果
        
        


        
        
        


        
        
        当选中cbAwait和cbConfigureAwait时。
        主方法调用子方法,进入await task.run用异步线程进行异步操作,当它完成时因为configureawait为真,即这个异步线程y必须交权,需要切换到调用者或UI线程上,即主方法的线程上去,这里主方法线程UI线程正在延时3秒处,没有空闲,线程y就要一直等它空闲,直到在messagebox.show时得到UI线程的空间,于是异步线程y就回线程池去了,而正在弹出信息框进的UI线程得到空间,就返回到子方法中继续向下执行,直到子方法最后的信息执行完成后,就返回到主方法中,继续向下,也就是print把信息打印出来。所以看到的信息是,主方法信息都完成了,最后才是子方法的信息。
        
        
        问:为什么说在messagebox.show得到了空闲呢?
        答:为了观察是什么时间追加的信息,我们做下面的修改:

        private void Test_ConfigureAwait(object sender, EventArgs e)
        {
            Stopwatch sw = Stopwatch.StartNew();
            string s1 = cbAwait.Checked.ToString();
            string s2 = cbConfigureAwait.Checked.ToString();
            strResult.Clear();
            strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】主线程开始:Await:{s1},ConfigureAwait:{s2}");
            ChildMethod();
            strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】主线程开始等待");
            Thread.Sleep(3000);
            sw.Stop();
            strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】主线程结束{sw.ElapsedMilliseconds}ms");
            strResult.AppendLine($"对话框前:{DateTime.Now.TimeOfDay}");
            MessageBox.Show(Owner, "主线程结束,输出结果", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
            strResult.AppendLine($"对话框后:{DateTime.Now.TimeOfDay}");
            Print(strResult.ToString());
        }

        private async void ChildMethod()
        {
            strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】ChildMethod开始......");
            Stopwatch sw = Stopwatch.StartNew();
            if (cbAwait.Checked)
            {
                await Task.Run(() =>
                {
                    strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】子线程开始......");
                    Thread.Sleep(2000);
                    strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】子线程延时2000ms结束");
                }).ConfigureAwait(cbConfigureAwait.Checked);
            }
            else
            {
                Task.Run(() =>
                {
                    strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】子线程开始......");
                    Thread.Sleep(2000);
                    strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】子线程延时2000ms结束");
                }).ConfigureAwait(cbConfigureAwait.Checked);
            }

            strResult.AppendLine($"子方法延时前{DateTime.Now.TimeOfDay}");
            Thread.Sleep(3000);
            strResult.AppendLine($"子方法延时后{DateTime.Now.TimeOfDay}");
            sw.Stop();
            strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】ChildMethod结束{sw.ElapsedMilliseconds}ms");
        }


    
        得出的结果是:
        


        
        可以看到异步线程在信息框时得到空闲,从而完成切换到UI中,UI中就到子方法中继续完成剩下的代码,这里面包括特意加了一个3秒的延时,它也得到了执行,直到子方法全部完成才回到了主方法中去打印print。
        
    
    
    

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Golang复习

文章目录 golang的特点golang数据类型基本数据类型(值类型)引用数据类型 make和newmakenew 浅拷贝,深拷贝深拷贝:实现深拷贝的方式:浅拷贝:实现浅拷贝的方式 接口接口是什么 某种类型可以比较吗channel数据…

flutter 里面自定义appbar

众所周知,fluuter里面的appbar 包含title, leading,action, bottom 如果我想在appbar里面自定一些内容,他会默认继承appbar的高度,且位置也没法自定义,如下图 这个时候我该怎么办? 在appbar里面将下面的内…

【JVM】对象死亡判断

文章目录 简述引用计数算法可达性分析算法4种对象引用finalize()方法回收方法区 简述 在堆里面存放着Java世界中几乎所有的对象实例,垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还“存活”着,哪些已经“死去”&#x…

(17)线程的实例认识:wait,waitany,waitall,及经典死锁问题

一、文件构成 1、界面:一个textbox,四个button。 2、程序:前面(15)的book类与data类 private void AppendLine(string s){txtInfo.AppendText(string.IsNullOrEmpty(txtInfo.Text) ? s : $"{Environment.NewLine}{s}");…

Redis 7 第八讲 集群模式(cluster)架构篇

集群架构 Redis 集群架构图 集群定义 Redis 集群是一个提供在多个Redis节点间共享数据的程序集;Redis集群可以支持多个master 应用场景 Redis集群支持多个master,每个master又可以挂载多个slave读写分离支持数据的高可用支持海量数据的读写存储操作集群自带Sentinel的故障…

看懂UML类图

UML 统一建模语言(Unified Modeling Language,UML)是一种为面向对象系统的产品进行说明、可视化和编制文档的一种标准语言,是非专利的第三代建模和规约语言。UML是面向对象设计的建模工具,独立于任何具体程序设计语言。 类的表示 首先看那个…

ROS2中如何从欧拉角转换成四元素

ROS1中使用from tf.transformations import quaternion_from_euler导入quaternion_from_euler()即可调用。而ROS2中默认没有安装,需要单独安装一下ros-galactic-tf-transformations(我使用的ROS2是galactic,根据版本名不同做相应修改即可): …

Python GUI入门——tkinter编辑框、复选框、下拉菜单和按钮文本框的使用

简介 接上一次的tkinter编写界面相关内容,丰富点常用控件的方法,学会了这些控件布局和相关方 法属性,能够满足日常小工具的制作需求了。 搭建的界面框架如下图所示,功能可以自己添加。 界面代码 # -*- coding: utf-8 -*- impor…

第五章 树于二叉树 七、树和森林的遍历(广度优先遍历、深度优先遍历)

1、树的遍历 树是一种递归定义的数据结构,所以我们可以使用递归实现遍历。 (1)先根遍历(最先访问根节点)(深度优先遍历) 1.使用孩子兄弟表示法将其转化为二叉树的形式。 2.使用先序遍历二叉树…

面试2:通用能力

15丨如何做好开场:给自我介绍加“特效 第一层,满足面试官对信息的期待 这是对自我介绍的基本要求,把个人信息、主要经历、经验和技能有条理地组织起来, 有逻辑地讲出来。需要找出多段经历的关联性和发展变化,形成连…

分布式事务之 Seata 的部署和集成

文章目录 一、部署Seata的tc-server1.下载2.解压3.修改配置4.在nacos添加配置5.创建数据库表6.启动TC服务 二、Docker 中跑 Seata三、微服务集成seata1.引入依赖2.修改配置文件 四、TC服务的高可用和异地容灾1.模拟异地容灾的TC集群2.将事务组映射配置到nacos3.微服务读取nacos…

Linux下go环境安装、环境配置并执行第一个go程序

一、安装 1.Golang对Linux的内核版本要求 GO对Linux内核版本最低要求是 2.6.23,对应要求操作系统版本是: RHEL 6.0CentOS 6.0即,不支持 (RHEL 和 CentOS) 的 (4.x or 5.x)。2.下载golang的代码版本 Golang的官网下载地址:https:…

Presto 之Pipeline

一. 前言 我们知道在Presto中有个叫Pipeline的概念,Pipeline其实就是一条包含各个算子的流水线,如下所示。本文主要介绍在Presto中,Pipeline是如何划分的。 二. Pipeline 在Presto中,一个Stage中包括一个或者多个Pipeline&#xf…

JavaWeb项目基础配置

这里写目录标题 前言1.pom.xml2.webxml3.连接JDBC4.常用代码4.1获取时间4.2实现上传文件需要用到第三方库fileupload 常用快捷键常用操作Tomcat基础操作&#xff1a; 前言 本文主要讲述了 1.pom.xml <?xml version"1.0" encoding"UTF-8"?><pro…

9.2QTday4作业

1 闹钟 头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QtDebug> #include <QLabel> //标签类 #include <QLineEdit> //行编辑器类 #include <QPushButton> //按钮类 #include <QTextEdit> //文本编辑器…

STM32定时器定时及其应用

STM32定时器定时及其应用 定时器概述☆定时器相关配置CubeMX工程配置及程序实现固件库程序设计及实现 定时器概述 1. 工作原理 使用精准的时基&#xff0c;通过硬件的方式&#xff0c;实现定时功能。定时器核心就是计数器 2. 定时器分类   基本定时器&#xff08;TIM6~TIM7…

SiameseNet实战中文文本匹配任务

引言 本文我们通过SiameseNet模型来完成中文文本匹配任务&#xff0c;其中包含了文本匹配任务一般套路&#xff0c;后续只需要修改实现的模型。 数据准备 数据准备包括 构建词表(Vocabulary)构建数据集(Dataset) 本次用的是LCQMC通用领域问题匹配数据集&#xff0c;它已经…

RK3568-android11-适配ov13850摄像头

硬件连接 主要分为两部分: mipi接口:传输摄像头数据 i2c接口:配置摄像头和对焦马达芯片寄存器相关驱动 |-- arch/arm64/boot/dts/rockchip DTS配置文件 |-- drivers/phy/rockchip/|-- phy-rockchip-mipi-rx.c mipi dphy 驱动 |-- drivers/media||-- platform/rockchip/isp1…

20230903-闹钟

app.cpp #include "app.h" #include "ui_app.h" int k1 true;APP::APP(QWidget *parent):QWidget(parent),ui(new Ui::APP) {ui->setupUi(this);this->resize(380,300);this->setStyleSheet("background-color:cyan;");//设置样式spe…

关于指针的一些练习(1)

1. int main() {int a[3][2] { (0,1),(2,3),(4,5) };int* p;p a[0];printf("%d", p[0]);return 0; } 解析&#xff1a;a是一个3行2列的二维数组&#xff0c;对他进行初始化时大括号里面是逗号表达式&#xff0c;根据逗号表达式可以得到a数组中元素为1&#xff0c…