C#的线程技术及操作(Thread类)

news2024/11/23 21:37:15

目录

一、线程基础

1.单线程 

2.多线程

(1)多线程的缺点

(2)多线程的缺点

二、线程操作之Thread类

1. Thread类的相关方法和属性

(1)示例源码

(2)生成效果

2.创建线程Start()方法

(1)示例源码

(2) 生成效果

​​​​​​3.线程的挂起与恢复Suspend()方法和Resume()方法

(1)Suspend()方法 

(2)Resume()方法

(3)该方法已废弃及替代方法

​​​​​4.线程休眠Sleep()方法

5.终止线程Abort()方法和Join()方法

(1)Abort()方法

(2)Join()方法


        每个正在操作系统上运行的应用程序都是一个进程一个进程可以包括一个或多个线程。线程是操作系统分配处理器时间的基本单元,在进程中可以有多个线程同时执行代码。每个线程都维护异常处理程序、调度优先级和一组系统用于在调度该线程前保存线程上下文的结构。线程上下文包括为使线程在线程的宿主进程地址空间中无缝地继续执行所需的所有信息,包括线程的CPU寄存器组和堆栈。

一、线程基础

1.单线程 

        单线程顾名思义,就是只有一个线程。默认情况下,系统为应用程序分配一个主线程,该线程执行程序中以Main()方法开始和结束的代码。

2.多线程

        需要用户交互的软件都必须尽可能快地对用户的活动做出反应,以便提供丰富多彩的用户体验,但同时它又必须执行必要的计算以便尽可能快地将数据呈现给用户,这时可以使用多线程来实现。

(1)多线程的缺点

要提高对用户的响应速度并且处理所需数据以便几乎同时完成工作,使用多线程是一种最为强大的技术,在具有一个处理器的计算机上,多线程可以通过利用用户事件之间很小的时间段在后台处理数据来达到这种效果。例如,通过使用多线程,在另一个线程正在重新计算同一应用程序中的电子表格的其他部分时,用户可以编辑该电子表格。

单个应用程序域可以使用多线程来完成以下任务。

☑ 通过网络与Web服务器和数据库进行通信。
☑ 执行占用大量时间的操作。
☑ 区分具有不同优先级的任务。
☑ 使用户界面可以在将时间分配给后台任务时仍能快速做出响应。 

(2)多线程的缺点

使用多线程有好处,同时也有坏处,建议一般不要在程序中使用太多的线程,这样可以最大限度地减少操作系统资源的使用,并可提高性能。

如果在程序中使用了多线程,可能会产生如下问题。

☑ 系统将为进程、AppDomain对象和线程所需的上下文信息使用内存。因此,可以创建的进程、AppDomain对象和线程的数目会受到可用内存的限制。
☑ 跟踪大量的线程将占用大量的处理器时间。如果线程过多,则其中大多数线程都不会产生明显的进度。如果大多数当前线程处于一个进程中,则其他进程中的线程的调度频率就会很低。
☑ 使用许多线程控制代码执行非常复杂,并可能产生许多bug。
☑ 销毁线程需要了解可能发生的问题并对那些问题进行处理。

二、线程操作之Thread

1. Thread类的相关方法和属性

        Thread类位于System.Threading命名空间下,System.Threading命名空间提供一些可以进行多线程编程的类和接口。除同步线程活动和访问数据的类(Mutex、Monitor、Interlocked和AutoResetEvent 等)外,该命名空间还包含一个ThreadPool类(它允许用户使用系统提供的线程池)和一个Timer类(它在线程池的线程上执行回调方法)。

        Thread类主要用于创建并控制线程、设置线程优先级并获取其状态。一个进程可以创建一个或多个线程以执行与该进程关联的部分程序代码,线程执行的程序代码由ThreadStart委托或ParameterizedThreadStart委托指定。

        线程运行期间,不同的时刻会表现为不同的状态,但它总是处于由ThreadState定义的一个或多个状态中。用户可以通过使用ThreadPriority枚举为线程定义优先级,但不能保证操作系统会接受该优先级。

        Thread类的常用属性及说明如表

属    性

说    明

    ApartmentState

   状取或设置此线程的单元状态

    CurrentContex

获取线程正在其中执行的当前上下文

    CurrentThread

   获取当前正在运行的线程

    isAlive

获取一个值,该值指示当前线程的执行状态

    ManagedThreadld

获取当前托管线程的唯一标识符

    Name

   获取或设置线程的名称

    Priority

获取或设置一个值,该值指示线程的调度优先级

    ThreadState

获取一个值,该值包含当前线程的状态

        Thread类的常用方法及说明如表

方   法

说   明

   Abort

在调用此方法的线程上引发ThreadAbortException,以开始终止此线程的过程。调用此方法通常会终止线程

   GetApartmentState

返回一个ApartmentState值,该值指示单元状态

   GetDomain

返回当前线程正在其中运行的当前域

   GetDomainID

返回唯一的应用程序域标识符

   Interrupt

中断处于WaitSleepJoin线程状态的线程

   Join

阻止调用线程,直到某个线程终止时为止

   ResetAbort

取消为当前线程请求的Abort

   Resume

维续已挂起的线程

   SetApartmentState

在线程启动前设置其单元状态

   Sleep

将当前线程阻止指定的毫秒数

   SpinWait

导致线程等待由iterations参数定义的时间量

   Start

使线程被安排进行执行

   Suspend

挂起线程,或者如果线程已挂起,则不起作用

   VolatileRead

读取字段值。无论处理器的数目或处理器缓存的状态如何,该值都是由计算机的任何处理器写入的最新值

   VolatileWrite

立即向字段写入一个值,以使该值对计算机中的所有处理器都可见

(1)示例源码

         使用Thread类的相关方法和属性,开始运行一个线程,并获得该线程的相关信息。

// 使用Thread类的相关方法和属性,
// 开始运行一个线程,并获得该线程的相关信息
using System.Threading;

namespace _01
{
    public partial class Form1 : Form
    {
        private Button? button1;
        private RichTextBox? richTextBox1;
        public CancellationToken cancellationToken;

        public Form1()       
        {
            InitializeComponent();
            Load += Form1_Load;
        }

        private void Form1_Load(object? sender, EventArgs e)
        {
            // 
            // button1
            // 
            button1 = new Button
            {
                Location = new Point(55, 126),
                Name = "button1",
                Size = new Size(75, 23),
                TabIndex = 0,
                Text = "确定",
                UseVisualStyleBackColor = true
            };
            button1.Click += Button1_Click;
            // 
            // richTextBox1
            // 
            richTextBox1 = new RichTextBox
            {
                Location = new Point(12, 12),
                Name = "richTextBox1",
                Size = new Size(160, 108),
                TabIndex = 1,
                Text = ""
            };
            // 
            // Form1
            // 
            AutoScaleDimensions = new SizeF(7F, 17F);
            AutoScaleMode = AutoScaleMode.Font;
            ClientSize = new Size(184, 161);
            Controls.Add(richTextBox1);
            Controls.Add(button1);
            Name = "Form1";
            StartPosition = FormStartPosition.CenterScreen;
            Text = "Form1";
        }

        private void Button1_Click(object? sender, EventArgs e)
        {
            string strlnfo = string.Empty;         //定义一个字符串,用来记录线程相关信息
            Thread myThread =new(new ThreadStart(ThreadOut));//实例化Thread线程类对象
            myThread.Start();                         //启动主线程
            strlnfo = "线程唯一标识符:" + myThread.ManagedThreadId; 
            strlnfo += "\n线程名称:" + myThread.Name;
            strlnfo += "\n线程状态;" + myThread.ThreadState.ToString(); 
            strlnfo += "\n线程优先级:" + myThread.Priority.ToString(); 
            strlnfo += "\n是否为后台线程:" + myThread.IsBackground; 
            Thread.Sleep(1000);                     //使主线程休眠1秒钟
            richTextBox1!.Text = strlnfo;
            ThreadAbort(cancellationToken);//通过主线程阻止新开线程
            //myThread.Abort("退出");           //SYSLIB0006:不支持 Thread.Abort
            myThread.Join();                         //等待新开的线程结束
            MessageBox.Show("线程运行结束");                                  
        }
        public static void ThreadOut()
        {
            MessageBox.Show("主线程开始运行");
        }
        /// <summary>
        /// 中止当前线程以外的线程
        /// </summary>
        /// <param name="cancellationToken">形参</param>
        static void ThreadAbort(CancellationToken cancellationToken)
        {
            // If the CancellationToken is marked as "needs to cancel",
            // this will throw the appropriate exception.
            cancellationToken.ThrowIfCancellationRequested();
        }
    }
}

(2)生成效果

         首先生成主线程Form1()

 

         按下确定后,开启第二个线程ThreadOut()

        再按下确定后,开启第三个线程ThreadAbort()

 

         再按下确定后退出第二、第三线程,只剩下主线程Form1()

 

2.创建线程Start()方法

        创建一个线程:只需将其声明并为其提供线程起始点处的方法委托即可。

        创建新的线程:需要使用Thread类,Thread类具有接受一个ThreadStart委托或ParameterizedThreadStart委托的构造函数,该委托包装了调用Start()方法时由新线程调用的方法。创建了Thread类的对象之后,线程对象已存在并已配置,但并未创建实际的线程,这时,只有在调用Start()方法后,才会创建实际的线程。

        Start()方法用来使线程被安排执行,它有两种重载形式:

  • 导致操作系统将当前实例的状态更改为ThreadState.Running,语法如下
publie void Start()
  • 使操作系统将当前实例的状态更改为ThreadState.Running,并选择提供包含线程执行的方法要使用的数据的对象,语法如下
public void Start (Object parameter)
parameter:一个对象,包含线程执行的方法要使用的数据。
  •          如果线程已经终止,就无法通过再次调用Start()方法来重新启动。

(1)示例源码

         中通过实例化Thread类对象创建一个新的线程。最后调用Start()方法启动该线程。

// 通过实例化Thread类对象创建一个新的线。
// 最后调用Start()方法启动该线程
namespace _02
{
    class Program
        {
        /// <summary>
        /// 用线程起始点的ThreadStart委托创建该线程的实例
        /// </summary>
        static void Main(string[] args)
        {
            Thread myThread;									//声明线程
            myThread = new Thread(new ThreadStart(CreateThread));
            myThread.Start();									//启动线程

            //myThread.Suspend();								//挂起线程, CS0618
            //myThread.Resume();							    //恢复挂起的线程, CS0618
        }

        public static void CreateThread()
        {
            Console.Write("创建线程");
        }
    }
}

(2) 生成效果

创建线程
F:\C#_TM\chapter25\02\bin\Debug\net8.0\02.exe (进程 20376)已退出,代码为 0。
按任意键关闭此窗口. . .

​​​​​​3.线程的挂起与恢复Suspend()方法和Resume()方法

        创建完一个线程并启动之后,还可以挂起、恢复、休眠或终止它,线程的挂起与恢复分别可以通过调用Thread类中的Suspend()方法和Resume()方法实现。

(1)Suspend()方法 

        Suspend()方法用来挂起线程,如果线程已挂起,则不起作用,语法如下。

public vaid Suspend()

        调用Suspend()方法挂起线程时,.NET允许要挂起的线程再执行几个指令,目的是为了到达.NET认为线程可以安全挂起的状态。

(2)Resume()方法

        Resume()方法用来继续已挂起的线程,语法如下。

public void Resume()

        通过Resume()方法来恢复被暂停的线程时,无论调用了多少次Suspend()方法,调用Resume()方法均会使另一个线程脱离挂起状态,并导致该线程继续执行。

(3)该方法已废弃及替代方法

        Thread.Suspend() 和 Thread.Resume() 这两个方法已过时。需要使用 AutoResetEvent,EventWaitHandle代替。

         废弃方法的示例在上例中注释掉的部分。替代方法的示例见作者其它文章。

​​​​​4.线程休眠Sleep()方法

        线程休眠主要通过Thread类的Sleep()方法实现,该方法用来将当前线程阻止指定的时间,它有两种重载形式:

        将当前线程挂起指定的时间,语法如下。

public static void Sleep/int millisecondsTimeout)
millisecondsTimeout:线程被阻止的毫秒数。指定零以指示应挂起此线程以使其他等待线程能够执行,指定Infinite以无限期阻止线程。 

        将当前线程阻止指定的时间,语法如下。

public static void Sleep(Time Span timeout)
timeout:线程被阻止的时间量的TimeSpan。指定零以指示应挂起此线程以使其他等待线程能够执行,指定Infinite以无限期阻止线程。

​​​​​​​5.终止线程Abort()方法和Join()方法

        终止线程可以分别使用Thread类的Abort()方法和Join()方法实现:

(1)Abort()方法

        Abort()方法用来终止线程,它有两种重载形式:

  • 终止线程,在调用此方法的线程上引发ThreadAbortException异常,以开始终止此线程的过程,语法如下。
public void Abort()
  • 终止线程,在调用此方法的线程上引发ThreadAbortException异常,以开始终止此线程并提供有关线程终止的异常信息的过程,语法如下。
public void Abort(Object statelnfo) 
stateInfo:一个对象,它包含应用程序特定的信息(如状态),该信息可供正被终止的线程使用。

         示例源码:

// 调用Thread类的Abort()方法终止已开启的线程
namespace _03
{
    class Program
    {
        /// <summary>
        /// 用线程起始点的ThreadStart委托创建该线程的实例
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            Thread myThread;									//声明线程
            myThread = new Thread(new ThreadStart(CreateThread));
            myThread.Start();                                   //启动线程
#pragma warning disable SYSLIB0006 // 类型或成员已过时
            myThread.Abort();                                   //终止线程
#pragma warning restore SYSLIB0006 // 类型或成员已过时
        }
        public static void CreateThread()
        {
            Console.Write("线程实例");
        }
    }

}

         生成效果,即使经过禁止处理,也不能生成,因为被弃用了啊:

线程实例Unhandled exception. System.PlatformNotSupportedException: Thread abort is not supported on this platform.
   at System.Threading.Thread.Abort()
   at _03.Program.Main(String[] args) in F:\C#_TM\chapter25\03\Program.cs:line 16

F:\C#_TM\chapter25\03\bin\Debug\net8.0\03.exe (进程 26936)已退出,代码为 -532462766。
按任意键关闭此窗口. . .

        线程的Abort()方法用于永久地停止托管线程。调用Abort()方法时,公共语言运行库在目标线程中引发ThreadAbortException异常,目标线程可捕捉此异常。一旦线程被终止,它将无法重新启动。

(2)Join()方法

        Join()方法用来阻止调用线程,直到某个线程终止时为止,它有3种重载形式:

  • 在继续执行标准的COM和SendMessage消息处理期间阻止调用线程,直到某个线程终止为止,语法如下。
public void Join()
  • 在继续执行标准的COM和SendMessage消息处理期间阻止调用线程,直到某个线程终止或经过了指定时间为止,语法如下。
public bool Join(int millisecondsTimeout)
☑ millisecondsTimeout:等待线程终止的毫秒数。
☑ 返回值:如果线程已终止,则为true;如果线程在经过了millisecondsTimeout参数指定的时间量后未终止,则为false。
  • 在继续执行标准的COM和SendMessage消息处理期间阻止调用线程,直到某个线程终止或经过了指定时间为止,语法如下。
public bool Join(TimeSpan timeout)
☑ timeout:等待线程终止的时间量的TimeSpan。
☑ 返回值:如果线程已终止,则为true;如果线程在经过了timeout参数指定的时间量后未终止,则为false。

        如果在应用程序中使用了多线程,辅助线程还没有执行完毕,在关闭窗体时必须关闭辅助线程,否则会引发异常。

        示例源码:

// 调用了Thread类的Join()方法等待线程终止
namespace _04
{
    class Program
    {
        /// <summary>
        /// 用线程起始点的ThreadStart委托创建该线程的实例
        /// </summary>
        static void Main(string[] args)
        {
            Thread myThread;									//声明线程
            myThread = new Thread(new ThreadStart(CreateThread));
            myThread.Start();									//启动线程
            myThread.Join();									//阻止调用该线程,直到该线程终止
        }
        public static void CreateThread()
        {
            Console.Write("线程实例");
        }
    }
}
线程实例
F:\C#_TM\chapter25\04\bin\Debug\net8.0\04.exe (进程 19932)已退出,代码为 0。
按任意键关闭此窗口. . .

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

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

相关文章

机器人制作开源方案 | “AIBOT”-多功能货物搬运机

作者&#xff1a;王晨曦、敖鑫、王威威、安尚琦 单位&#xff1a;北京理工大学 指导老师&#xff1a;李忠新、朱杰 一、场景调研 如今搬运机在我们的生产作业及生活中都起着比较重要的作用&#xff0c;在现代社会对升降机需求的增多&#xff0c;市场的不断扩大&#xff0c;使…

2.5 常规游戏中模型通用要求介绍

一、布线和理性 多星点&#xff08;4个及4个以上边的交点&#xff09; 如果是在中模阶段&#xff0c;减少使用多星点&#xff0c;因为会在细分是时出现凸点问题&#xff0c;如果要使用多星点&#xff0c;需要通过布线技巧把它移动至平面处&#xff0c;不要让他出现在倒角边缘。…

基于SSM医院员工考勤管理系统,请假系统,员工管理系统,部门管理系统(Java毕业设计)

大家好&#xff0c;我是DeBug&#xff0c;很高兴你能来阅读&#xff01;作为一名热爱编程的程序员&#xff0c;我希望通过这些教学笔记与大家分享我的编程经验和知识。在这里&#xff0c;我将会结合实际项目经验&#xff0c;分享编程技巧、最佳实践以及解决问题的方法。无论你是…

电流测量原理

由于直接测量电流信号是很难的&#xff0c;但是测试电压信号比较容易&#xff0c;因此通常都是先将电流信号转换为电压信号&#xff0c;将电压信号进行调理后送至 CPU&#xff0c;CPU 通过 AD 转换得到一个码值&#xff0c;软件读出该码值&#xff0c;先根据主控的硬件设计参数…

1688一件采购实现指南:含代码实现采购流程

一、引言 1688是中国最大的B2B电子商务平台之一&#xff0c;提供了丰富的商品信息和采购服务。一键采购是1688平台的一项便捷功能&#xff0c;可以帮助用户快速完成采购流程&#xff0c;提高采购效率。本文将详细介绍如何使用1688一键采购功能&#xff0c;并通过代码示例演示如…

语音群呼有哪些常见的应用场景?

事务通知提醒 银行信用卡还款通知、帐户进出帐通知等&#xff1b;供电部门电费收缴通知、客户查询、公告通知等&#xff1b;通信企业话费欠费通知。其他行业如&#xff1a;物业行业、自来水行业、人社局、法院等事务通知。 预警通知 譬如台风、防汛、防洪、地震、海啸、泥石…

AppLink+WMS,实现仓储管理一体化

WMS像全能的库管员&#xff0c;可以在线还原真实仓库&#xff0c;让企业进行科学化、条理化、俯视化的仓库管理。 随着移动互联网和物流行业的快速发展&#xff0c;如何提高仓储管理的效率和准确性成为了企业关注的焦点。在这个背景下&#xff0c;结合AppLink和WMS系统&#x…

windows 网卡设备收包禁止自动剥掉VLAN

windows网卡驱动默认会在接收数据包的时候剥掉vlan tag&#xff0c;使得用wireshark抓到的数据包中不含vlan tag&#xff0c;此时需要通过修改注册表让驱动保留vlan tag。 1. 打开网络连接 找到需要修改的网卡。属性-配置-详细信息-驱动程序关键字。复制{4d36e972-e325-11ce-…

10款AI绘画软件,一次性打包给你!

科技的发展如疾风骤雨&#xff0c;人工智能的影响已渗透到我们生活的各个角落。其中&#xff0c;艺术界同样在这股大潮中得到了翻天覆地的改变。人工智能与艺术的结合&#xff0c;开辟出了科技与艺术融合的全新视野。例如&#xff0c;人工智能绘画软件&#xff0c;这种类型的软…

使用@ExceptionHandler对数据库异常进行统一处理

使用ExceptionHandler对数据库异常进行统一处理 数据库插入异常信息,插入字段长短超时数据库设置字段长度 ### Error updating database. Cause: com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: Data too long for column remark at row 1捕获异常处理…

【T1】T1飞跃专业版软件中, 操作添加现金银行信息时提示:该父类账户信息有财务数据,不能分级。

【问题描述】 在T1飞跃专业版软件中&#xff0c; 操作添加现金银行信息时提示&#xff1a;该父类账户信息有财务数据&#xff0c;不能分级。 【解决方法】 该问题是原因是因为T1和U通用财务联用。 Ps&#xff1a;留一下软件版本【飞跃19.5财务15.0.2.5】 针对账套库执行下属语…

【含泪分享】OpenCV4工业缺陷检测的六种方法

机器视觉 机器视觉是使用各种工业相机&#xff0c;结合传感器跟电气信号实现替代传统人工&#xff0c;完成对象识别、计数、测量、缺陷检测、引导定位与抓取等任务。其中工业品的缺陷检测极大的依赖人工完成&#xff0c;特别是传统的3C制造环节&#xff0c;产品缺陷检测依赖于…

PyQt6 QSlider滑块控件

锋哥原创的PyQt6视频教程&#xff1a; 2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~共计45条视频&#xff0c;包括&#xff1a;2024版 PyQt6 Python桌面开发 视频教程(无废话版…

【数据结构—栈的实现(数组栈)】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 一、栈 1.1栈的概念及结构 二、栈的实现 2.1头文件的实现—Stack.h 2.2源文件的实现—Stack.c 2.3源文件的测试—test.c 三、栈的实际测试数据展示 3.1正常的出…

好用便签类工具有哪些?让上班族更轻松一些

想象一下你坐在电脑前&#xff0c;正在为即将到来的会议做准备&#xff0c;你需要整理一些重要的信息&#xff0c;以免在会议中遗漏关键内容。可是&#xff0c;你的电脑桌面上堆满了各种文件和图标&#xff0c;让你感到有些凌乱。这时&#xff0c;好用的便签类工具就成了你的救…

使用Allure框架创建个性化测试报告的步骤

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

FindMy技术用于手表

手表是一种用于计时和显示时间的仪器&#xff0c;通常由机械、电子或石英等机制驱动。手表可以作为配饰佩戴在手腕上&#xff0c;具有方便携带、时尚美观等特点。手表的种类和款式很多&#xff0c;可以根据不同的需求和场合进行选择。常见的手表类型包括机械表、石英表、电子表…

如何进行产品数据分析一——移动应用APP分析方法

如何进行产品数据分析 产品的定义产品分析的构成移动应用APP分析方法AARRR1.流量拆解DAUMAU活跃率拆解流量深度 2.流量引入反作弊算法识别系统&#xff08;量&#xff09;拉新质量评估体系&#xff08;质&#xff09;渠道价值评估体系&#xff08;值&#xff09; 3.流量输出 产…

同义词替换器降低论文重复率的最新技术进展

大家好&#xff0c;今天来聊聊同义词替换器降低论文重复率的最新技术进展&#xff0c;希望能给大家提供一点参考。 以下是针对论文重复率高的情况&#xff0c;提供一些修改建议和技巧&#xff1a; 标题&#xff1a;同义词替换器降低论文重复率的最新技术进展 一、引言 随着学术…

RK3568全国产化多网口板卡带poe供电,支持鸿蒙麒麟系统

信迈XM-3568-01主板采用瑞芯微RK3568四核Cortex-A55 处理器&#xff0c;主频最高可达2.0GHz&#xff0c;效能有大幅提升最高可配8GB内存容量&#xff0c;频率高达1600MHz&#xff1b;支持全链路ECC&#xff0c;让数据更安全可靠配置双千兆自适应RJ45以太网口&#xff0c;并扩展…