(十)异步-使用异步(3)

news2024/12/24 11:27:24

一、GUI 程序中的异步操作

1、在 GUI 程序中使用异步操作

在 GUI程序中, 首先理解关于 UI 显示变化的概念。

  • 消息: UI 上的行为,如点击按钮、展示标签、移动窗体等。
  • 消息队列: 把要触发的所有消息,都按照相关的顺序放入到里面。
  • 消息泵: 消息泵主要从队列里提取出并处理该消息。
  • 处理程序代码: 与消息“绑定”的实现代码,如点击按钮消息,执行程序代码:btnDoStuff_Click。

因为在GUI 程序上对所有UI效果的变化都必须在主 GUI 线程中完成的,所以 UI主线程通过消息泵来对UI消息进行处理。

消息泵从队列中取出一条消息,并调用它的处理程序代码。当处理程序代码完成时,消息泵获取下一条消息并循环这个过程。

请添加图片描述

但是如果某个消息的处理程序代码耗时过长,消息队列中的消息会产生积压(消息没有及时进行移除并处理),程序将会一时失去响应,在等待当前处理程序代码处理完成之前。没法处理其他任何消息。

以下是WPF 应用程序中处理程序代码中使用了 Thread.Sleep 的示例:

xaml代码:

    <StackPanel>
        <Label Name="lbStatus" Margin="10,5,10,0">Not Doing Anything</Label>
        <Button Name="btnDoStuff" Content="Do Stuff" HorizontalAlignment="Left" Margin="10,5" Padding="5,2" Click="btnDoStuff_Click"></Button>
    </StackPanel>

后台cs代码:

private void btnDoStuff_Click(object sender, RoutedEventArgs e)
        {
            btnDoStuff.IsEnabled = false;
            lbStatus.Content = "Doing Stuff";
            Thread.Sleep(4000);
            lbStatus.Content = "Not Doing Anything";
            btnDoStuff.IsEnabled = true;
        }

运行结果:

lbStatus 标签看起来没有改变,是因为还没有及时把刷新标签内容的消息进行处理,就直接暂停线程了。4秒后线程重新工作,这时其实是有把标签改变为 Doing Stuff 的,但是处理的时间太快,就立马执行下一条刷新的消息,所以肉眼上看不出内容改变的效果。如果把 lbStatus.Content = “Not Doing Anything”; 这条语句注释掉,就会发现4秒后标签就显示为 Doing Stuff 。

请添加图片描述

如果把 Thread.Sleep 改为Task.Delay,点击按钮后就不会卡顿。这是因为 Thread.Sleep 会阻塞线程,而 Task.Delay 不会阻塞线程(暂时不处理对应消息的处理程序代码),线程还可以继续处理其他工作。(其他触发的消息)

2、Task.Yield

Task.Yield 方法创建一个立即返回的 awaitable。当异步方法在处理程序代码里被调用时,异步方法里的 每次执行一个 await Task.Yield,就会把当前消息从消息队列中移除,再回到队列末尾,移交控制权给其他任务由处理器继续处理。
跟 Task.Delay 不同的一点就是:

  • 调用一次 Task.Yield 就会把消息重新回到队列末尾中,重新在等到它时才会继续处理后续部分代码。
  • 调用Task.Delay 设置一定时间内,先处理其他消息,等时间到了,才会继续处理它的后续部分代码。

使用异步 Lambda 表达式

XAML:

<StackPanel>
       <TextBlock Name="workStartedTextBlock" Margin="10,10"/>
        <Button Name="StartWorkButton" Width="100" Margin="4" Content="Start Work"/>
</StackPanel>

后台 CS 代码:

 public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            //异步 Lambda 表达式:async (sender, e) =>
            StartWorkButton.Click += async (sender, e) =>
            {
                SetGuiValues(false, "Work Started");
                await DoSomeWork();
                SetGuiValues(true, "Work Finished");
            };
        }

        private void SetGuiValues(bool buttonEnabled,string status)
        {
            StartWorkButton.IsEnabled = buttonEnabled;
            workStartedTextBlock.Text = status;
        }

        private Task DoSomeWork()
        {
            return Task.Delay(2500);
        }
    }

二、BackgroundWorker 类

1、BackgroundWorker 类

该类创建一个新线程,是属于后台线程,在后台持续运行以完成某项工作,并不时地与主线程通信。

请添加图片描述

类的主要成员:
属性:

  • WorkerReportsProgress 和 WorkerSupportsCancellation: 这两个属性用于设置后台任务是否可以把它的进度汇报给主线程以及是否支持从主线程取消。
  • IsBusy: 检查后台任务是否正在运行。

事件:
用于发送不同的程序事件和状态。

  • DoWork:后台线程开始的时候触发。
  • ProgressChanged:后台任务汇报进度的时候触发。
  • RunWorkerCompleted:后台工作线程退出的时候触发。

方法:
用于开始行为或改变状态。

  • RunWorkerAsync: RunWorkerAsync 方法获取后台线程并且执行 DoWork 事件处理程序。

  • CancelAsync: 调用 CancelAsync 方法把 CancellationPending 属性设置为 true。DoWork 事件处理程序需要检查这个属性来决定是否应该停止处理。

  • DoWork: DoWork 事件处理程序(在后台线程)在希望向主线程汇报进度的时候,调用 ReportProgress 方法。

DoWork 是必需的,该事件跟事件处理程序关联,包含有在后台线程执行的代码。
ProgressChanged 和 RunWorkerCompleted 是可选的,取决于程序的需要。

后台线程处理程序的原理:

  • DoWork 事件包含后台线程上执行的代码。

    • 主线程调用 BackgroundWorker 对象的 RunWorkerAsync 方法的时候会触发 DoWork 事件。
  • 后台线程通过调用 ReportProgress 方法与主线程通信。届时将触发 ProgressChanged 事件,主线程可以附加到 ProgressChanged 事件上的处理程序处理事件。

  • 附加到 RunWorkerCompleted 事件的处理程序应该包含在后台线程完成 DoWork 事件处理程序的执行之后需要执行的代码。(即结束该后台线程工作的最后一个事情)

请添加图片描述

主要事件的声明如下:

void DoWorkEventHandler(object sender, DoWorkEventArgs e)
void ProgressChangedEventHandler(object sender, ProgressChangedEventArgs e)
void RunWorkerCompletedEventHandler(object sender, RunWorkerCompletedEventArgs e)

请添加图片描述

2、配置 BackgroundWorker 类对象:

  • 若需要工作线程向主线程汇报进度,把 WorkerReportsProgress 属性设置为 true。(启用的话,会调用 ReportProgress 方法,从而触发 ProgressChanged 事件。)

  • 若从主线程取消工作线程,就把 WorkerSuppoortsCancellation 属性设置为 true。(启用的话,DoWork 事件处理程序代码会定期检查 CancellationPending 属性是否取消了。若是,则退出。)

类对象配置好后,调用 RunWorkerAsync 方法来启动它。

在 WPF 程序中使用 BackgroundWorker 类的示例:

XAML 代码:

   <StackPanel>
     <ProgressBar Name="progressBar" Height="20" Width="200" Margin="100"/>
        <Button Name="btnProcess" Width="100" Click="btnProcess_Click"
                Margin="5">Process</Button>
        <Button Name="btnCancel" Width="100" Click="btnCancel_Click"
                Margin="5">Cancel</Button>
    </StackPanel>

后台 cs 代码:

    public partial class MainWindow : Window
    {
        BackgroundWorker bgWorker = new BackgroundWorker();
        public MainWindow()
        {
            InitializeComponent();

            //设置 BackgroundWorker 属性
            bgWorker.WorkerReportsProgress = true;
            bgWorker.WorkerSupportsCancellation = true;

            //连接 BackgroundWorker 对象的处理程序
            bgWorker.DoWork += DoWork_Handler;
            bgWorker.ProgressChanged += ProgressChanged_Handler;
            bgWorker.RunWorkerCompleted += RunWorkerCompleted_Handler;
        }

        private void btnProcess_Click(object sender, RoutedEventArgs e)
        {
            if (!bgWorker.IsBusy)
                bgWorker.RunWorkerAsync();
        }

        private void btnCancel_Click(object sender, RoutedEventArgs e)
        {
            bgWorker.CancelAsync();
        }

        private void ProgressChanged_Handler(object sender,ProgressChangedEventArgs args)
        {
            progressBar.Value = args.ProgressPercentage;
        }

        private void DoWork_Handler(object sender, DoWorkEventArgs args)
        {
            BackgroundWorker worker = sender as BackgroundWorker;

            for(int i = 1; i <= 10;i++)
            {
                if(worker.CancellationPending)
                {
                    args.Cancel = true;
                    break;
                }
                else
                {
                    worker.ReportProgress(i * 10);
                    Thread.Sleep(500);//这里当前线程为后台线程
                }
            }
        }

        private void RunWorkerCompleted_Handler(object sender, RunWorkerCompletedEventArgs args)
        {
            progressBar.Value = 0;
            if (args.Cancelled)
                MessageBox.Show("Process was cancelled.", "Process Cancelled");
            else
                MessageBox.Show("Process completed normally.", "Process Completed");
        }

    }

三、并行循环

任务并行库是 BCL 中的一个类库。
如果迭代之间彼此独立,并且程序运行在多处理器机器上。

使用条件: 迭代之间彼此独立。每=次迭代运行的过程和结果都互相不影响彼此,如计算多个不同范围的面积。若每次迭代都叠加数值最终得出一个总数,则不适合使用并行循环。

1、Parallel.For:

public static ParallelLoopResult.For(int fromInclusive, int toExclusive, Action body);
  • fromInclusive: 迭代系列的第一个整数。
  • toExclusive: 比迭代系列最后一个索引号大1的整数。和使用表达式 index<ToExclusive 计算一样。
  • body: 接受单个输入参数的委托,body 的代码在每一次迭代中执行一次。
class Program
{
static void Main()
{
Parallel.For(0,15,i =>
			Console.WriteLine($"The square of{ i } is { i * i}"));
}
}

输出结果:

The square of 0 is 0
The square of 1 is 1
The square of 2 is 4
The square of 4 is 16
The square of 5 is 25
The square of 6 is 36
The square of 7 is 49
The square of 8 is 64
The square of 9 is 81
The square of 10 is 100
The square of 11 is 121
The square of 12 is 144
The square of 13 is 169
The square of 14 is 196
The square of 3 is 9

整数数组的示例:

class Program
{
static void Main()
{
const int maxValues = 50;
int[] squares = new int[maxValues];

Parallel.For(0,maxValues,i => squares[i] = i *i);
}
}

2、Parallel.ForEach

Parallel.ForEach 方法有相当多的重载,其中最简单的如下:

static ParallelLoopResult ForEach<TSource>(IEnumerable<TSource> source,Action<TSource> body)

示例:

    class Program
    {
        static void Main(string[] args)
        {
            string[] squares = new string[]
                { "We","hold","these","truths","to","be","self-evident",
                  "that","all","men","are","created","equal"};

            Parallel.ForEach(squares, s => Console.WriteLine(string.Format($"\"{ s }\" has { s.Length } letters")));
            Console.ReadKey();
        }
    }

输出结果:

“We” has 2 letters
“truths” has 6 letters
“to” has 2 letters
“be” has 2 letters
“self-evident” has 12 letters
“that” has 4 letters
“all” has 3 letters
“men” has 3 letters
“are” has 3 letters
“created” has 7 letters
“equal” has 5 letters
“hold” has 4 letters
“these” has 5 letters

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

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

相关文章

【备战秋招】每日一题:2023.05-B卷-华为OD机试 - 阿里巴巴找黄金宝箱(II)

为了更好的阅读体检&#xff0c;可以查看我的算法学习博客阿里巴巴找宝箱(ll) 题目描述 贫如洗的樵夫阿里巴巴在去砍柴的路上&#xff0c;无意中发现了强盗集团的藏宝地&#xff0c;藏宝地有编号从0-N的箱子&#xff0c;每个箱子上面贴有箱子中藏有金币的数量。 从金币数量中…

【MySQL】索引的数据结构

为什么使用索引 索引是存储引擎用于快速找到数据记录的一种数据结构。进行数据查找时&#xff0c;首先查看查询条件是否命中某条索引&#xff0c;符合则可以通过索引查找相关数据&#xff0c;如果不符合则要全表扫描&#xff0c;即需要一条一条地查找记录&#xff0c;直到找到…

「网络编程」第二讲:网络编程socket套接字(二)_ 简单UDP网络通信程序的实现

「前言」文章是关于网络编程的socket套接字方面的&#xff0c;上一篇是网络编程socket套接字&#xff08;一&#xff09;&#xff0c;下面开始讲解&#xff01; 「归属专栏」网络编程 「笔者」枫叶先生(fy) 「座右铭」前行路上修真我 「枫叶先生有点文青病」 「每篇一句」 我认…

chatgpt赋能python:Python遍历文章的SEO指南

Python遍历文章的SEO指南 Python是一种高度灵活的编程语言&#xff0c;因其易于学习和使用而为许多程序员、数据科学家和SEO专业人士所青睐。在这篇文章中&#xff0c;我们将探讨Python如何遍历文章和对SEO优化的最佳实践。 什么是Python遍历文章&#xff1f; 遍历文章是指采…

【论文阅读】Segment Anything(SAM)——可分割一切的CV大模型

【前言】随着ChatGPT席卷自然语言处理&#xff0c;Facebook凭借着Segment Anything在CV圈也算扳回一城。迄今为止&#xff0c;github的star已经超过3万&#xff0c;火的可谓一塌糊涂。作为AI菜鸟&#xff0c;可不得自己爬到巨人肩膀上瞅一瞅~ 论文地址&#xff1a;https://arxi…

NDK编译C++源码生成Android平台so文件(opencv_android)

1.准备CPP文件 编写CMakeLists.txt 编写 mk文件 android-8 is unsupported. Using minimum supported version android-16 APP_PLATFORM android-16 is higher than android:minSdkVersion 1 in ./AndroidManifest.xml 修改Application.mk中的APP_PLATFORM为 android-21 builg…

59、基于51单片机多机 NRF24L01 无线温湿度 DHT11报警系统设计(程序+原理图+PCB源文件+参考论文+开题报告+任务书+元器件清单等)

摘 要 温湿度在工农业生产中占有很重要的地位&#xff0c;是工农业生产的重要组成数据。温湿度过高会造成粮食发霉长芽&#xff0c;还会引起大棚蔬菜一系列的病害。因此对其适时准确的测量就显得尤为重要。而一般的测量过程较为复杂繁琐&#xff0c;误差还大。比如现在所使…

MySQL-SQL存储过程/触发器详解(上)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a; 小刘主页 ♥️努力不一定有回报&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️学习两年总结出的运维经验&#xff0c;以及思科模拟器全套网络实验教程。专栏&#xf…

Python 循环与判断(详解)

❄️作者介绍&#xff1a;奇妙的大歪❄️ &#x1f380;个人名言&#xff1a;但行前路&#xff0c;不负韶华&#xff01;&#x1f380; &#x1f43d;个人简介&#xff1a;云计算网络运维专业人员&#x1f43d; 前言 在Python中&#xff0c;循环语句有两个&#xff0c;一个是fo…

Redis缓存穿透-击穿-雪崩详细分析加解决办法

Redis 缓存穿透 问题描述-如图 缓存穿透的原因 key 对应的数据在数据源并不存在&#xff0c;每次针对此key 的请求从缓存获取不到&#xff0c;请求都会压到数据源, 可能压垮数据源比如: 用一个不存在的用户id 获取用户信息&#xff0c;不论缓存还是数据库都没有&#xff0c;…

Spring Boot 集成 Redisson分布式锁(拿来即用版)

Redisson 是一种基于 Redis 的 Java 驻留集群的分布式对象和服务库&#xff0c;可以为我们提供丰富的分布式锁和线程安全集合的实现。在 Spring Boot 应用程序中使用 Redisson 可以方便地实现分布式应用程序的某些方面&#xff0c;例如分布式锁、分布式集合、分布式事件发布和订…

JSON5的作用、安装及使用

JSON5是对JSON的扩展&#xff0c;让人可以更容易手工编写和维护&#xff0c;用来减少一些JSON的限制&#xff0c;诸如json语法不支持注释&#xff0c;不支持字符串换行&#xff0c;所有的key都必须双引号&#xff0c;末尾不能有多余的逗号…等等&#xff0c;一大堆极其严格的要…

chatgpt赋能python:Python的退役与SEO

Python的退役与SEO 随着Python编程语言的流行和普及&#xff0c;越来越多的人开始使用它来开发各种类型的应用程序。但是&#xff0c;就像我们所知道的&#xff0c;所有技术都会发生变化&#xff0c;包括编程语言。因此&#xff0c;Python程式员可能会感到困惑和担忧&#xff…

JavaScript对象 (八):对象类型的使用、值类型和引用类型、函数的this指向、工厂方法创建对象、构造函数和类、new创建对象

1. 对象类型的使用 1.1 认识对象类型 基础数据类型可以存储一些简单的值&#xff0c;但是现实世界的事物抽象成程序时&#xff0c;往往比较复杂。 比如一个人&#xff0c;有自己的特性&#xff08;比如姓名、年龄、身高&#xff09;&#xff0c;有一些行为&#xff08;比如跑…

课程17:菜单管理功能实现

🚀前言 本文是《.Net Core从零学习搭建权限管理系统》教程专栏的课程(点击链接,跳转到专栏主页,欢迎订阅,持续更新…) 专栏介绍:以实战为线索,基于.Net 7 + REST + Vue、前后端分离,不依赖任何第三方框架,从零一步一步讲解权限管理系统搭建。 专栏适用于人群:We…

为什么运行时安全性重新流行起来?

容器通过以更高效和可扩展的方式创建、打包和部署应用程序&#xff0c;彻底改变了软件开发过程。 然而&#xff0c;能力越大&#xff0c;责任越大&#xff0c;对“左移安全性”的高度关注为那些在运行时忽视安全性的组织带来了风险。 通过对容器运行时安全采取多层次、全面的…

云服务器ECS_云主机_服务器托管_弹性计算-阿里云

阿里云服务器ECS&#xff08;Elastic Compute Service&#xff09;是一种安全可靠、弹性可伸缩的云计算服务&#xff0c;阿里云提供多种云服务器ECS实例规格&#xff0c;如通用算力型u1、ECS计算型c7、通用型g7、GPU实例等&#xff0c;阿里云服务器网分享阿里云服务器ECS详细介…

Linux文件操作四剑客

目录 一、grep &#xff08;一&#xff09;作用 &#xff08;二&#xff09;格式 &#xff08;三&#xff09;选项 &#xff08;四&#xff09;案例 1、查看/etc目录下所有包含bash的文件名&#xff1a;grep -rl bash /etc 2、查看/var/log目录下所有包含error的文…

读发布!设计与部署稳定的分布式系统(第2版)笔记04_集成点

1. 第一个拥有10亿用户的网站 1.1. 2016年&#xff0c;Facebook宣布其每日活跃用户数量为11.3亿 1.2. 对整个应用程序来说&#xff0c;“五个9”的可靠性远远不够&#xff0c;这每天会让成千上万的用户失望 1.3. 假如按照六西格玛质量标准来衡量&#xff0c;那么Facebook每天…

LIN-网络管理:休眠(Go To Sleep)和唤醒(Wake up)

文章目录 一、LIN总线的两种状态二、休眠模式&#xff08;Go To Sleep&#xff09;①利用诊断帧中的主机请求帧 0x3C 作休眠命令②当总线静默(没有显性和隐性电平之间的切换)4s&#xff5e;10s 时&#xff0c;节点自动进入休眠状态。 三、唤醒模式&#xff08;Wake up&#xff…