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


    继续(15)的例子

一、ConfigureAwait()的作用

        private async void BtnAsync_Click(object sender, EventArgs e)//异步{Stopwatch sw = Stopwatch.StartNew();TxtInfo.Clear();AppendLine("异步检索开始...");AppendLine($"当前线程Id:{Environment.CurrentManagedThreadId}");//bint idx = 0;foreach (var b in Data.Books){string t = await Task.Run(b.Search).ConfigureAwait(false);//aAppendLineThread($"{++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.rhkb.cn/news/124553.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

springboot使用切面记录接口访问日志

前言 当我们开发和维护一个复杂的应用程序时,了解应用程序的运行情况变得至关重要。特别是在生产环境中,我们需要追踪应用程序的各个方面,以确保它正常运行并能够及时发现潜在的问题。其中之一关键的方面是记录应用程序的接口访问日志。 Sp…

函数相关概念

4.函数 1.函数的概念 1.什么是函数? 把特点的代码片段,抽取成为独立运行的实体 2.使用函数的好处1.重复使用,提供效率2.提高代码的可读性3.有利用程序的维护 3.函数的分类1.内置函数(系统函数)已经提高的alert(); prompt();confirm();print()document.write(),console.log()…

http和https区别,第三方证书如何保证服务器可信

目录 HTTP和HTTPS有以下区别: 第三方证书如何保证服务器的可信性 需要注意哪些方面 可能遇到什么问题 随着互联网技术的不断发展,数据传输的安全性越来越受到人们的关注。HTTP和HTTPS是两种常用的网络协议,它们之间的主要区别在于数据传输…

【图文并茂】c++介绍之队列

1.1队列的定义 队列(queue)简称队,它也是一种操作受限的线性表,其限制为仅允许在表的一端进行插入操作,而在表的另一端进行删除操作 一些基础概念: 队尾(rear) :进行插…

CloudQuery X PolarDB:让数据库管理更简单

前言:8 月 15 日,CloudQuery 数据操作管控平台与阿里云 PolarDB 数据库管理软件,完成产品集成认证测试。也在以下功能上完善了用户使用 PolarDB 的体验,使数据库的管理更加安全高效。 支持在 CloudQuery 中创建连接,便…

数据接口工程对接BI可视化大屏(一)

文章目录 第1章 案例概述1.1 案例目标1.2 BI最终效果1.2.1 PC端显示效果1.2.2 移动端显示效果 后记 第1章 案例概述 1.1 案例目标 此项目以常见的手机零售BI场景为例,介绍如何编写数据接口工程对接BI可视化大屏。 如何从当前常见的主流大数据场景中为后台程序推送…

数据结构入门-13-图

文章目录 一、图的概述1.1 图论的作用1.2 图的分类1.2.1 无向图1.2.2 有向图1.2.3 无权图1.2.4 有劝图 1.3 图的基本概念 二、树的基本表示2.1 邻接矩阵2.1.1 邻接矩阵 表示图2.1.2 邻接矩阵的复杂度 2.2 邻接表2.2.1 邻接表的复杂度2.2.2 邻接表By哈希表 三、图的深度优先遍历…

sentinel加密狗使用及规则配置

Sentinel加密狗是一种硬件加密设备,用于保护软件应用程序免受未经授权的访问和复制。它可以提供软件许可管理、访问控制和数据保护等功能。下面是Sentinel加密狗的使用及规则配置的相关介绍。 Sentinel加密狗的使用 插入加密狗:将Sentinel加密狗插入计算…

C51智能小车(循迹、跟随、避障、测速、蓝牙、wifie、4g、语音识别)总结

目录 1.电机模块开发 1.1 让小车动起来 1.2 串口控制小车方向 1.3 如何进行小车PWM调速 1.4 PWM方式实现小车转向 2.循迹小车 2.1 循迹模块使用 2.2 循迹小车原理 2.3 循迹小车核心代码 3.跟随/避障小车 3.1 红外壁障模块分析​编辑 3.2 跟随小车的原理 3.3 跟随小…

Leetcode.174 地下城游戏

题目链接 Leetcode.174 地下城游戏 hard 题目描述 恶魔们抓住了公主并将她关在了地下城 d u n g e o n dungeon dungeon 的 右下角 。地下城是由 m x n 个房间组成的二维网格。我们英勇的骑士最初被安置在 左上角 的房间里,他必须穿过地下城并通过对抗恶魔来拯救公…

【高阶产品策略】设计有效的AB测试

文章目录 1、A/B测试概述2、A/B测试实施过程3、A/B测试中需要注意的地方4、从一个案例中看A/B测试 1、A/B测试概述 2、A/B测试实施过程 3、A/B测试中需要注意的地方 4、从一个案例中看A/B测试

编写中间件以用于 Express 应用程序

概述 中间件函数能够访问请求对象 (req)、响应对象 (res) 以及应用程序的请求/响应循环中的下一个中间件函数。下一个中间件函数通常由名为 next 的变量来表示。 中间件函数可以执行以下任务: 执行任何代码。对请求和响应对象进行更改。结束请求/响应循环。调用堆…

pytorch学习——循环神经网络RNN讲解及其实现

参考书籍:8.6. 循环神经网络的简洁实现 — 动手学深度学习 2.0.0 documentation 参考视频:54 循环神经网络 RNN【动手学深度学习v2】_哔哩哔哩_bilibili 一.介绍 循环神经网络RNN(Recurrent Neural Network )是一类广泛应用于序列…

stm32之30.DMA

DMA(硬件加速方法)一般用于帮运比较大的数据(如:摄像头数据图像传输),寄存器-》DMA-》RAM 或者 RAM-》DMA-》寄存器提高CPU的工作效率 源码-- #include "myhead.h" #include "adc.h"#…

javaee之黑马乐优商城2

简单分析一下商品分类表的结构 先来说一下分类表与品牌表之间的关系 再来说一下分类表和品牌表与商品表之间的关系 面我们要开始就要创建sql语句了嘛,这里我们分析一下字段 用到的数据库是heima->tb_category这个表 现在去数据库里面创建好这张表 下面我们再去编…

有向图和无向图的表示方式(邻接矩阵,邻接表)

目录 一.邻接矩阵 1.无向图​编辑 2.有向图 补充:网(有权图)的邻接矩阵表示法 二.邻接表 1.无向图 2.有向图 三.邻接矩阵与邻接表的关系 一.邻接矩阵 1.无向图 (1)对角线上是每一个顶点与自身之间的关系&…

线性空间和线性变化

目录 考点一、线性空间的基与维数 1、线性空间 2、基底 3、子空间(线性子空间) ​编辑4、生成子空间 (1)、v1 n v2 (2)、v1 v2 5、求和子空间的方法 6、维数定理 7、例题 (1&#xf…

HCIA自学笔记01-冲突域

共享式网络(用同一根同轴电缆通信)中可能会出现信号冲突现象。 如图是一个10BASE5以太网,每个主机都是用同一根同轴电缆来与其它主机进行通信,因此,这里的同轴电缆又被称为共享介质,相应的网络被称为共享介…

MyBatis-Plus学习笔记总结

一、查询 构造器分为QueryWrapper和LambdaQueryWrapper 创建实体类User package com.system.mybatisplus.model;import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.…

JDK8的 ConcurrentHashMap 源码分析

目录 1. 导读 2. ConcurrentHashMap 成员变量解读 3. ConcurrentHashMap 初始化 3.1 ConcurrentHashMap 无参构造源码解读 3.2 ConcurrentHashMap 带参构造源码解读 3.3 tableSizeFor 方法作用解读 3.4 ConcurrenthashMap初始化总结 4. ConcurrentHashMap 添加元素方法…