【C#进阶】C# 多线程

时间:2021-01-12 01:20:17
序号 系列文章
19 【C#进阶】C# 集合类
20 【C#进阶】C# 泛型
21 【C#进阶】C# 匿名方法

前言

???? hello大家好,我是哈桑c,本文为大家介绍 C# 中的多线程。


1、线程与多线程的基本概念

线程是操作系统能够进行运算调度的最小单位,它被包含在进程1之中,是进程中的实际运行单位。一个线程指的是进程中一个单一顺序的控制流,一个进程可以并发2多个线程,每个线程并行执行不同的任务。

多线程是指在软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,进而提升整体处理性能的能力。

在 C# 中,实现多线程技术最常使用的类就是包含在 System.Threading 命名空间中的 Thread 类。Thread 类是一个定义创建和控制线程,设置其优先级并获取其状态的类。以一个简单的程序演示 Thread 类的用法,借此讨论多线程的使用。

代码示例:

using System;
using System.Threading;

// 简单的线程场景:启动一个静态方法运行在第二个线程上。
public class ThreadExample
{
    // 被运行的子线程
    public static void ThreadProc()
    {
        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine("子线程: {0}", i);
            Thread.Sleep(0);
        }
    }

    public static void Main()
    {
        Console.WriteLine("主线程:开启第二个线程。");
        
        Thread t = new Thread(new ThreadStart(ThreadProc));
        t.Start();      // 开启子线程的工作
        
        // 同时开启主线程的工作
        for (int i = 0; i < 4; i++)
        {
            Console.WriteLine("主线程:开始工作");
            Thread.Sleep(0);
        }

        Console.WriteLine("主线程:调用Join(),等待子线程结束。");
        t.Join();
        Console.WriteLine("主线程:子线程已经 Join 回来了。按Enter键结束程序。");
        Console.ReadLine();
    }
}

运行结果:
【C#进阶】C# 多线程
在上例中可以看出,使用 Thread 类我们不仅可以执行主线程3的内容,还可以创建新的 Thread 对象以此来运行子线程4的内容。这样我们就成功实现了一个多线程程序。

2、创建并使用线程

在 Thread 类中,创建子线程可以通过 Thread t = new Thread(); 调用构造方法的方式来实现。扩展的 Thread 类调用 Start() 方法来开始子线程的执行。

代码示例:

using System;
using System.Diagnostics;
using System.Threading;

public class Example
{
    public static void Main()
    {
        var th = new Thread(ExecuteInForeground);
        th.Start(4500);
        Thread.Sleep(1000);
        Console.WriteLine("主线程 ({0}) 等待...",
                          Thread.CurrentThread.ManagedThreadId);
    }

    private static void ExecuteInForeground(Object obj)
    {
        int interval;
        try
        {
            interval = (int)obj;
        }
        catch (InvalidCastException)
        {
            interval = 5000;
        }

        var sw = Stopwatch.StartNew();
        Console.WriteLine("线程 {0}: {1}, 属性 {2}",
                          Thread.CurrentThread.ManagedThreadId,
                          Thread.CurrentThread.ThreadState,
                          Thread.CurrentThread.Priority);
        do
        {
            Console.WriteLine("线程 {0}: 运行 {1:N2} 描述",
                              Thread.CurrentThread.ManagedThreadId,
                              sw.ElapsedMilliseconds / 1000.0);
            Thread.Sleep(500);
        } while (sw.ElapsedMilliseconds <= interval);
        sw.Stop();
    }
}

运行结果:
【C#进阶】C# 多线程
在上例中,我们使用 var th = new Thread(ExecuteInForeground); 的方法并传入了 ExecuteInForeground 的构造方法名成功的创建了一个子线程
【C#进阶】C# 多线程
同时我们也可以使用 Thread 类中 start 方法来启动子线程的运行。注意如果方法需要有参数的话,那么这时就需要在 start 方法上传入参数,如果没有则反之。
【C#进阶】C# 多线程

3、检索线程对象

在使用 Thread 类时,如果想要获取线程的信息,可以从正在执行的代码中使用 Thread 类的属性来检索当前正在运行的线程的引用对象

代码示例:

using System;
using System.Threading;

public class ExampleThread
{
    // 创建一个obj对象便于用于锁机制
    static Object obj = new Object();

    public static void Main()
    {
        ThreadPool.QueueUserWorkItem(ShowThreadInformation);    // 将方法排入队列以便执行。 
        var th1 = new Thread(ShowThreadInformation);
        th1.Start();
        var th2 = new Thread(ShowThreadInformation);
        th2.IsBackground = true;
        th2.Start();
        Thread.Sleep(500);
        ShowThreadInformation(null);
    }

    private static void ShowThreadInformation(Object state)
    {
        lock (obj)      // 为保证线程之间的执行顺序,在这里加上一个锁
        {
            var th = Thread.CurrentThread;
            Console.WriteLine("托管线程 #{0}: ", th.ManagedThreadId);
            Console.WriteLine("后台线程: {0}", th.IsBackground);
            Console.WriteLine("线程池线程: {0}", th.IsThreadPoolThread);
            Console.WriteLine("优先级: {0}", th.Priority);
            Console.WriteLine("文化: {0}", th.CurrentCulture.Name);
            Console.WriteLine("UI文化: {0}", th.CurrentUICulture.Name);
            Console.WriteLine();
        }
    }
}

运行结果:
【C#进阶】C# 多线程
从上例中可以看到,我们不仅创建了托管线程,还创建了前台线程、后台线程以及线程池的对象。在这里我们使用了 ManagedThreadId 等属性输出了线程的 Id 值、是否为后台线程或线程池5、优先级和线程名等线程信息。
【C#进阶】C# 多线程

4、前台线程和后台线程

在 .NET 框架的公用语言运行时(Common Language Runtime,CLR)中能区分两种不同类型的线程:前台线程和后台线程。

前台线程和后台线程的区别:

  • 如果所有前台线程已终止,后台线程不会使进程保持运行。
  • 停止所有前台线程后,运行时将停止所有后台线程并关闭。

前台线程和后台线程分别的应用范围。

默认情况下,以下线程在前台执行:

  • 主线程: 常见的主应用程序线程均为前台线程。
  • 子线程: 通过调用类构造函数创建 Thread 的所有线程。

默认情况下,以下线程在后台执行:

  • 线程池线程: 由运行时维护的工作线程池。 可以使用 类配置线程池并计划线程池线程 ThreadPool 上的工作。
  • 非托管到托管的线程: 从非托管代码进入托管执行环境的所有线程。

在 Thread 类中,可以通过设置 IsBackground 的属性来更改在后台执行的线程。后台线程适用于只要应用程序正在运行就应继续执行但不阻止应用程序终止的任何操作,例如监视文件系统更改或传入套接字连接。

代码示例:

using System;
using System.Diagnostics;
using System.Threading;

public class BackgroundThreadExample
{
    public static void Main()
    {
        var th = new Thread(ExecuteInForeground);
        th.IsBackground = true;
        th.Start();
        Thread.Sleep(1000);
        Console.WriteLine("主线程 ({0}) 等待...", 
                        Thread.CurrentThread.ManagedThreadId); 
    }

    private static void ExecuteInForeground()
    {
        var sw = Stopwatch.StartNew();
        Console.WriteLine("线程 {0}: {1}, 优先级 {2}",
                          Thread.CurrentThread.ManagedThreadId,
                          Thread.CurrentThread.ThreadState,
                          Thread.CurrentThread.Priority);
        do
        {
            Console.WriteLine("Thread {0}: Elapsed {1:N2} seconds",
                              Thread.CurrentThread.ManagedThreadId,
                              sw.ElapsedMilliseconds / 1000.0);
            Thread.Sleep(500);
        } while (sw.ElapsedMilliseconds <= 5000);
        sw.Stop();
    }
}

运行结果:
【C#进阶】C# 多线程
从上例中可以看出,我们使用了 Thread 类对象的 IsBackground 属性将 th 线程对象设置为了后台线程。不像前面的示例一样 th 对象可以顺利执行不超过五秒的内容,在上例中可以看到 th 对象只执行了 0.51 秒(并不固定)。这是因为停止所有前台线程后,运行时将停止所有后台线程并关闭。所以在主线程(前台线程)输出"主线程 (1) 等待…"之后就表示主线程停止了,后台线程也会停止并关闭,并不会执行不超过五秒的内容。
【C#进阶】C# 多线程

5、Thread 类的属性和方法

以一个表格展示 Thread 类常用的属性和方法。

Thread 类常用的属性:

属性名 描述
ApartmentState 获取或设置此线程的单元状态。
CurrentCulture 获取或设置当前线程的区域性。
CurrentPrincipal 获取或设置线程的当前负责人(对基于角色的安全性而言)。
CurrentThread 获取当前正在运行的线程。
CurrentUICulture 获取或设置资源管理器使用的当前区域性以便在运行时查找区域性特定的资源。
ExecutionContext 获取 ExecutionContext 对象,该对象包含有关当前线程的各种上下文的信息。
IsAlive 获取指示当前线程的执行状态的值。
IsBackground 获取或设置一个值,该值指示某个线程是否为后台线程。
IsThreadPoolThread 获取指示线程是否属于托管线程池的值。
ManagedThreadId 获取当前托管线程的唯一标识符。
Name 获取或设置线程的名称。
Priority 获取或设置指示线程的调度优先级的值。
ThreadState 获取一个值,该值包含当前线程的状态。

Thread 类常用的方法:

方法名 描述
Abort() 在调用此方法的线程上引发 ThreadAbortException,以开始终止此线程的过程。 调用此方法通常会终止线程。
Equals(Object) 确定指定对象是否等于当前对象。(继承自 Object)
Finalize() 确保垃圾回收器回收 Thread 对象时释放资源并执行其他清理操作。
GetApartmentState() 返回表示单元状态的 ApartmentState 值。
GetCompressedStack() 返回 CompressedStack 对象,此对象可用于获取当前线程的堆栈。
GetCurrentProcessorId() 获取用于指示当前线程正在哪个处理器上执行的 ID。
GetDomain() 返回当前线程正在其中运行的当前域。
GetDomainID() 返回唯一的应用程序域标识符。
GetHashCode() 返回当前线程的哈希代码。
GetType() 获取当前实例的 Type。(继承自 Object)
Interrupt() 中断处于 WaitSleepJoin 线程状态的线程。
Join() 在继续执行标准的 COM 和 SendMessage 消息泵处理期间,阻止调用线程,直到由该实例表示的线程终止。
MemberwiseClone() 创建当前 Object 的浅表副本。(继承自 Object)
ResetAbort() 取消当前线程所请求的 Abort(Object)。
Resume() 继续已挂起的线程。
SetApartmentState(ApartmentState) 在线程启动前设置其单元状态。
SetCompressedStack(CompressedStack) 将捕获的 CompressedStack 应用到当前线程。
Sleep(Int32) 将当前线程挂起指定的毫秒数。
SpinWait(Int32) 导致线程等待由 iterations 参数定义的时间量。
Start() 导致操作系统将当前实例的状态更改为 Running。
ToString() 返回表示当前对象的字符串。(继承自 Object)
TrySetApartmentState(ApartmentState) 在线程启动前设置其单元状态。
UnsafeStart() 导致操作系统将当前实例的状态更改为 Running。
VolatileRead(Byte) 向字段中读取值。
VolatileWrite(Byte, Byte) 向字段中写入值。

点击了解更多多线程的使用。


结语

???? 以上就是 C# 多线程的介绍啦,希望对大家有所帮助。感谢大家的支持。


  1. 进程: 计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配的基本单位,是操作系统结构的基础。 ↩︎

  2. 并发: 指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。 ↩︎

  3. 主线程: 进程中第一个被执行的线程称为主线程。 ↩︎

  4. 子线程: 除了主线程外,在 C# 中使用 Thread t = new Thread(); 方式创建的线程均为子线程。 ↩︎

  5. 线程池: 是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。 ↩︎