C# - 多线程
线程 被定义为程序的执行路径。每个线程定义了一个唯一的控制流。如果您的应用程序涉及复杂且耗时的操作,那么设置不同的执行路径或线程通常会有所帮助,每个线程执行特定的任务。
线程是 轻量级进程。线程的一个常见用法示例是现代操作系统中并发编程的实现。使用线程可以节省 CPU 周期的浪费,并提高应用程序的效率。
到目前为止,我们编写的程序都是单个线程作为单个进程运行,这是应用程序的运行实例。然而,这种方式应用程序一次只能执行一项任务。为了让它同时执行多项任务,可以将其分成更小的线程。
线程生命周期
线程的生命周期从创建 System.Threading.Thread 类的对象开始,到线程终止或完成执行时结束。
以下是线程生命周期中的各种状态 −
未启动状态 − 这是创建线程实例但尚未调用 Start 方法的情况。
就绪状态 − 这是线程准备运行并等待 CPU 周期的情况。
不可运行状态 − 当以下情况发生时,线程不可执行:
- 已调用 Sleep 方法
- 已调用 Wait 方法
- 被 I/O 操作阻塞
死亡状态 − 这是线程完成执行或被中止的情况。
主线程
在 C# 中,System.Threading.Thread 类用于处理线程。它允许在多线程应用程序中创建和访问单个线程。进程中第一个执行的线程称为 主 线程。
当 C# 程序开始执行时,主线程会自动创建。使用 Thread 类创建的线程称为主线程的子线程。您可以使用 Thread 类的 CurrentThread 属性访问线程。
示例
以下程序演示了主线程的执行 −
using System;
using System.Threading;
namespace MultithreadingApplication {
class MainThreadProgram {
static void Main(string[] args) {
Thread th = Thread.CurrentThread;
th.Name = "MainThread";
Console.WriteLine("This is {0}", th.Name);
Console.ReadKey();
}
}
}
上述代码编译并执行后,将产生以下结果 −
This is MainThread
Thread 类的属性和方法
下表展示了 Thread 类最常用的某些 properties −
| 序号 | 属性 & 描述 |
|---|---|
| 1 | CurrentContext 获取当前线程正在执行的上下文。 |
| 2 | CurrentCulture 获取或设置当前线程的区域性。 |
| 3 | CurrentPrinciple 获取或设置线程的当前主体(用于基于角色的安全)。 |
| 4 | CurrentThread 获取当前正在运行的线程。 |
| 5 | CurrentUICulture 获取或设置 Resource Manager 在运行时用于查找特定区域性资源的当前区域性。 |
| 6 | ExecutionContext 获取一个 ExecutionContext 对象,该对象包含当前线程的各种上下文信息。 |
| 7 | IsAlive 获取一个值,表示当前线程的执行状态。 |
| 8 | IsBackground 获取或设置一个值,表示线程是否为后台线程。 |
| 9 | IsThreadPoolThread 获取一个值,表示线程是否属于托管线程池。 |
| 10 | ManagedThreadId 获取当前托管线程的唯一标识符。 |
| 11 | Name 获取或设置线程的名称。 |
| 12 | Priority 获取或设置一个值,表示线程的调度优先级。 |
| 13 | ThreadState 获取一个值,包含当前线程的状态。 |
下表展示了 Thread 类最常用的某些 methods −
| 序号 | 方法 & 描述 |
|---|---|
| 1 | public void Abort() 在调用该方法的线程中引发 ThreadAbortException,以开始终止线程的过程。通常调用此方法会终止线程。 |
| 2 | public static LocalDataStoreSlot AllocateDataSlot() 在所有线程上分配一个未命名的數據槽。为了获得更好的性能,请改用标记了 ThreadStaticAttribute 属性的字段。 |
| 3 | public static LocalDataStoreSlot AllocateNamedDataSlot(string name) 在所有线程上分配一个命名的數據槽。为了获得更好的性能,请改用标记了 ThreadStaticAttribute 属性的字段。 |
| 4 | public static void BeginCriticalRegion() 通知主机,执行即将进入一段代码区域,在该区域中,线程中止或未处理异常的影响可能会危及其他应用程序域中的任务。 |
| 5 | public static void BeginThreadAffinity() 通知主机,托管代码即将执行依赖于当前物理操作系统线程身份的指令。 |
| 6 | public static void EndCriticalRegion() 通知主机,执行即将进入一段代码区域,在该区域中,线程中止或未处理异常的影响仅限于当前任务。 |
| 7 | public static void EndThreadAffinity() 通知主机,托管代码已完成执行依赖于当前物理操作系统线程身份的指令。 |
| 8 | public static void FreeNamedDataSlot(string name) 消除进程中所有线程的名称与槽之间的关联。为了获得更好的性能,请改用标记了 ThreadStaticAttribute 属性的字段。 |
| 9 | public static Object GetData(LocalDataStoreSlot slot) 从当前线程的当前域中指定槽检索值。为了获得更好的性能,请改用标记了 ThreadStaticAttribute 属性的字段。 |
| 10 | public static AppDomain GetDomain() 返回当前线程正在运行的当前域。 |
| 11 | public static AppDomain GetDomainID() 返回唯一的应用程序域标识符 |
| 12 | public static LocalDataStoreSlot GetNamedDataSlot(string name) 查找命名的數據槽。为了获得更好的性能,请改用标记了 ThreadStaticAttribute 属性的字段。 |
| 13 | public void Interrupt() 中断处于 WaitSleepJoin 线程状态的线程。 |
| 14 | public void Join() 阻塞调用线程,直到线程终止,同时继续执行标准的 COM 和 SendMessage 泵送。此方法有不同的重载形式。 |
| 15 | public static void MemoryBarrier() 同步内存访问如下:执行当前线程的处理器不能以这样的方式重新排序指令,即 MemoryBarrier 调用之前的内存访问在 MemoryBarrier 调用之后的内存访问之后执行。 |
| 16 | public static void ResetAbort() 取消对当前线程的 Abort 请求。 |
| 17 | public static void SetData(LocalDataStoreSlot slot, Object data) 在当前运行的线程的当前域中设置指定槽的数据。为了获得更好的性能,请改用标记了 ThreadStaticAttribute 属性的字段。 |
| 18 | public void Start() 启动线程。 |
| 19 | public static void Sleep(int millisecondsTimeout) 使线程暂停一段时间。 |
| 20 | public static void SpinWait(int iterations) 使线程等待 iterations 参数定义的次数 |
| 21 | public static byte VolatileRead(ref byte address) public static double VolatileRead(ref double address) public static int VolatileRead(ref int address) public static Object VolatileRead(ref Object address) 读取字段的值。该值是计算机中任何处理器最新写入的值,无论处理器数量或处理器缓存状态如何。此方法有不同的重载形式。上面仅列出部分。 |
| 22 | public static void VolatileWrite(ref byte address,byte value) public static void VolatileWrite(ref double address, double value) public static void VolatileWrite(ref int address, int value) public static void VolatileWrite(ref Object address, Object value) 立即将值写入字段,使该值对计算机中的所有处理器可见。此方法有不同的重载形式。上面仅列出部分。 |
| 23 | public static bool Yield() 使调用线程将执行权让给当前处理器上准备运行的另一个线程。操作系统选择让出的线程。 |
创建线程
线程是通过扩展 Thread class 来创建的。扩展后的 Thread class 然后调用 Start() 方法来开始子线程执行。
示例
以下程序演示了这个概念 −
using System;
using System.Threading;
namespace MultithreadingApplication {
class ThreadCreationProgram {
public static void CallToChildThread() {
Console.WriteLine("Child thread starts");
}
static void Main(string[] args) {
ThreadStart childref = new ThreadStart(CallToChildThread);
Console.WriteLine("In Main: Creating the Child thread");
Thread childThread = new Thread(childref);
childThread.Start();
Console.ReadKey();
}
}
}
当上述代码编译并执行时,会产生以下结果 −
In Main: Creating the Child thread Child thread starts
向线程传递参数
您可以使用 ParameterizedThreadStart 委托或 lambda expression 向线程方法传递参数。
示例
以下示例演示了如何使用 lambda expression 向线程传递参数:
using System;
using System.Threading;
class Program
{
static void Main()
{
Thread thread = new Thread(() => PerformTask("Hello from thread!"));
thread.Start();
Console.WriteLine("Main thread is running.");
}
static void PerformTask(string message)
{
Console.WriteLine(message);
}
}
当上述代码编译并执行时,会产生以下结果 −
Hello from thread! Main thread is running.
管理线程
Thread class 提供了各种方法来管理线程。
示例
以下示例演示了使用 sleep() 方法使线程暂停特定时间段的使用。
using System;
using System.Threading;
namespace MultithreadingApplication {
class ThreadCreationProgram {
public static void CallToChildThread() {
Console.WriteLine("Child thread starts");
// 线程暂停 5000 毫秒
int sleepfor = 5000;
Console.WriteLine("Child Thread Paused for {0} seconds", sleepfor / 1000);
Thread.Sleep(sleepfor);
Console.WriteLine("Child thread resumes");
}
static void Main(string[] args) {
ThreadStart childref = new ThreadStart(CallToChildThread);
Console.WriteLine("In Main: Creating the Child thread");
Thread childThread = new Thread(childref);
childThread.Start();
Console.ReadKey();
}
}
}
当上述代码编译并执行时,会产生以下结果 −
In Main: Creating the Child thread Child thread starts Child Thread Paused for 5 seconds Child thread resumes
销毁线程
Abort() 方法用于销毁线程。
运行时通过抛出 ThreadAbortException 来中止线程。此异常无法被捕获,控制权会传递到 finally 块(如果存在)。
示例
以下程序演示了这一点 −
using System;
using System.Threading;
namespace MultithreadingApplication {
class ThreadCreationProgram {
public static void CallToChildThread() {
try {
Console.WriteLine("Child thread starts");
// 执行一些工作,比如数到 10
for (int counter = 0; counter <= 10; counter++) {
Thread.Sleep(500);
Console.WriteLine(counter);
}
Console.WriteLine("Child Thread Completed");
} catch (ThreadAbortException e) {
Console.WriteLine("Thread Abort Exception");
} finally {
Console.WriteLine("无法捕获线程异常");
}
}
static void Main(string[] args) {
ThreadStart childref = new ThreadStart(CallToChildThread);
Console.WriteLine("In Main: Creating the Child thread");
Thread childThread = new Thread(childref);
childThread.Start();
//让主线程暂停一段时间
Thread.Sleep(2000);
//现在中止子线程
Console.WriteLine("In Main: Aborting the Child thread");
childThread.Abort();
Console.ReadKey();
}
}
}
上述代码编译并执行后,会产生以下结果 −
In Main: Creating the Child thread Child thread starts 0 1 2 In Main: Aborting the Child thread Thread Abort Exception Couldn't catch the Thread Exception