目录
一、线程基础
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类的常用方法及说明如表
方 法 | 说 明 |
| 在调用此方法的线程上引发ThreadAbortException,以开始终止此线程的过程。调用此方法通常会终止线程 |
GetApartmentState | 返回一个ApartmentState值,该值指示单元状态 |
GetDomain | 返回当前线程正在其中运行的当前域 |
GetDomainID | 返回唯一的应用程序域标识符 |
Interrupt | 中断处于WaitSleepJoin线程状态的线程 |
Join | 阻止调用线程,直到某个线程终止时为止 |
| 取消为当前线程请求的Abort |
| 维续已挂起的线程 |
SetApartmentState | 在线程启动前设置其单元状态 |
Sleep | 将当前线程阻止指定的毫秒数 |
SpinWait | 导致线程等待由iterations参数定义的时间量 |
Start | 使线程被安排进行执行 |
| 挂起线程,或者如果线程已挂起,则不起作用 |
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。
按任意键关闭此窗口. . .