聊透多线程编程-线程基础-2.C# Thread类详解

 

目录

一、基本概念

二、基本用法

三、生命周期

四、线程的状态

五、线程的优先级

六、线程的前台与后台

七、常用的属性和方法

八、Sleep()、Join()、Interrupt()方法详解

1. Sleep 方法

2. Join 方法

3. Interrupt 方法

九、线程休眠

十、线程暂停与恢复

方法1:使用标志变量

方法2:使用 ManualResetEvent 或 AutoResetEvent

十一、线程取消

十二、线程安全与同步

十三、注意事项


Thread 类是 C# 中用于实现多线程编程的核心类之一,位于 System.Threading 命名空间下。通过 Thread 类,开发者可以创建和管理线程,从而实现并发执行任务的能力。

一、基本概念

  • 线程 是操作系统能够进行运算调度的最小单位。
  • 在 C# 中,主线程是程序启动时自动创建的线程。通过创建额外的线程,可以让程序同时执行多个任务。
  • 使用 Thread 类可以显式地创建和控制线程。

 

二、基本用法

要使用 Thread 类,需要提供一个方法(或委托),该方法将在线程中运行。

using System;
using System.Threading;

class Program
{
    static void Main(string[] args)
    {
        // 创建一个新的线程
        Thread thread = new Thread(new ThreadStart(DoWork));

        // 启动线程
        thread.Start();

        // 主线程继续执行
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine("Main thread: " + i);
            Thread.Sleep(500); // 模拟工作
        }

        // 等待子线程完成
        thread.Join();
        Console.WriteLine("Main thread finished.");
    }

    static void DoWork()
    {
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine("Worker thread: " + i);
            Thread.Sleep(500); // 模拟工作
        }
    }
}

输出示例:

Main thread: 0
Worker thread: 0
Main thread: 1
Worker thread: 1
Main thread: 2
Worker thread: 2
Main thread: 3
Worker thread: 3
Main thread: 4
Worker thread: 4
Main thread finished.

三、生命周期

线程的生命周期包括以下几个阶段:

  1. 未启动(Unstarted):线程对象已创建,但尚未启动。
  2. 运行中(Running):线程已启动并在执行任务。
  3. 挂起(Suspended):线程被暂停(不推荐使用)。
  4. 停止(Stopped):线程已完成或被终止。
  5. 阻塞(Blocked):线程等待某些资源或事件。

四、线程的状态

可以通过Thread.ThreadState属性获取线程的当前状态。例如:

Console.WriteLine(thread.ThreadState);

常见的状态值包括:

  • Unstarted:线程尚未启动。
  • Running:线程正在运行。
  • WaitSleepJoin:线程处于等待、睡眠或加入状态。
  • Stopped:线程已停止。

五、线程的优先级

可以通过Thread.Priority属性设置线程的优先级。优先级的值包括:

  • Lowest
  • BelowNormal
  • Normal(默认)
  • AboveNormal
  • Highest
thread.Priority = ThreadPriority.Highest;

六、线程的前台与后台

线程可以分为前台线程后台线程

  • 前台线程:即使主线程结束,前台线程也会继续运行,直到完成。
  • 后台线程:当所有前台线程结束时,后台线程会被强制终止。

通过Thread.IsBackground属性设置线程为后台线程:

thread.IsBackground = true;

七、常用的属性和方法

属性:

  • Thread.CurrentThread: 获取当前正在运行的线程。
  • IsAlive: 检查线程是否仍在运行。
  • Name: 设置或获取线程的名称。
  • Priority: 设置或获取线程的优先级(如 ThreadPriority.Normal)。
  • ThreadState: 获取线程的状态(如 Running, Stopped 等)。

方法:

  • Start(): 启动线程。
  • Join(): 阻塞调用线程,直到当前线程完成。
  • Sleep(int milliseconds): 暂停当前线程指定的时间。
  • Abort(): 终止线程(已过时,不推荐使用)。
  • Interrupt(): 中断处于等待状态的线程。
  • Suspend() 和 Resume(): 分别暂停和恢复线程(已过时,不推荐使用)。

 

说明:

Thread.Suspend() 和 Thread.Resume(): 这两个方法已经被废弃。它们的主要问题是不可预测的行为,因为它们允许一个线程无条件地挂起另一个线程,而不考虑该线程当前的状态或它是否持有任何锁。这可能导致死锁或其他复杂的同步问题。如果需要暂停和恢复线程,应该寻找其他解决方案,比如使用更高层次的同步原语或者重新设计应用逻辑来避免这种需求。

Thread.Abort(): 虽然严格来说没有被完全废弃,但是它的使用也被强烈不推荐。调用 Thread.Abort() 可能导致线程在不安全的地方终止,可能会留下未释放的资源或者使程序处于不稳定状态。现代的做法倾向于通过协作的方式来停止线程,例如设置标志位让线程自行检查并退出。

 

八、Sleep()、Join()、Interrupt()方法详解

1. Sleep 方法

详见后文“线程休眠”。

2. Join 方法

含义:Thread.Join 方法会阻塞调用线程,直到被调用的线程完成为止。这个方法对于等待子线程完成其工作非常有用,确保主线程或其它线程只有在所有必要的任务完成后才继续执行。

用法:

Thread thread = new Thread(new ThreadStart(SomeMethod));
thread.Start();
thread.Join(); // 主线程将在此处等待,直到thread线程完成

如果需要,你还可以指定一个超时时间,这样如果子线程在指定时间内没有完成,主线程也会继续执行:

bool didJoin = thread.Join(5000); // 等待最多5秒
if (didJoin)
{
    Console.WriteLine("Thread completed.");
}
else
{
    Console.WriteLine("Thread timed out.");
}

3. Interrupt 方法

含义:Thread.Interrupt 方法用于中断处于等待状态(如通过调用 Sleep、Wait 或 Join 而进入等待状态)的线程。这会导致等待中的线程抛出 ThreadInterruptedException 异常。注意,如果你尝试中断一个未处于等待状态的线程,那么中断请求会被记住,并且当该线程最终进入等待状态时,它将立即抛出异常。

用法

bool didJoin = thread.Join(5000); // 等待最多5秒
if (didJoin)
{
    Console.WriteLine("Thread completed.");
}
else
{
    Console.WriteLine("Thread timed out.");
}

在另一个线程中,你可以这样中断上述线程:

someThread.Interrupt(); // 中断someThread线程

使用 Interrupt 方法时要小心处理异常情况,并确保线程能够正确恢复或退出。

 

九、线程休眠

Thread.Sleep 方法使得当前正在执行的线程暂停执行指定的时间(以毫秒为单位)。在这段时间内,该线程不会占用CPU资源,但它的状态仍保留在内存中。

用法

Thread.Sleep(1000); // 当前线程暂停1秒(1000毫秒)

这种方法通常用来简单地延迟线程的执行,或者给其他线程机会来执行(例如,在循环中定期调用,以避免独占CPU资源)。

十、线程暂停与恢复

方法1:使用标志变量

通过共享的标志变量来控制线程的执行状态。这种方法简单且安全,因为线程会自行检查标志变量并决定是否暂停。

using System;
using System.Threading;

class Program
{
    private static bool _isPaused = false; // 标志变量
    private static readonly object _lock = new object(); // 锁对象,用于同步

    static void Main()
    {
        Thread thread = new Thread(DoWork);
        thread.Start();

        Thread.Sleep(2000); // 主线程等待2秒
        Console.WriteLine("暂停线程...");
        PauseThread();

        Thread.Sleep(3000); // 主线程等待3秒
        Console.WriteLine("恢复线程...");
        ResumeThread();

        thread.Join(); // 等待工作线程完成(在这个例子中不会结束)
        Console.WriteLine("主线程结束...");
    }

    static void DoWork()
    {
        while (true)
        {
            lock (_lock) // 使用锁保护对 _isPaused 的访问
            {
                if (_isPaused)
                {
                    Console.WriteLine("线程已暂停...");
                }
            }

            if (!_isPaused)
            {
                Console.WriteLine("线程正在运行...");
                Thread.Sleep(500); // 模拟工作
            }
            else
            {
                Thread.Sleep(100); // 当线程暂停时减少CPU占用
            }
        }
    }

    static void PauseThread()
    {
        lock (_lock)
        {
            _isPaused = true; // 设置暂停标志
        }
    }

    static void ResumeThread()
    {
        lock (_lock)
        {
            _isPaused = false; // 清除暂停标志
        }
    }
}

方法2:使用 ManualResetEvent 或 AutoResetEvent

anualResetEvent 和 AutoResetEvent 是信号量机制,可以用来实现线程的暂停和恢复。它们提供了一种线程间通信的方式。

using System;
using System.Threading;

class Program
{
    private static ManualResetEvent _pauseEvent = new ManualResetEvent(true); // 初始状态为未阻塞

    static void Main()
    {
        Thread thread = new Thread(DoWork);
        thread.Start();

        Thread.Sleep(2000); // 主线程等待2秒
        Console.WriteLine("暂停线程...");
        _pauseEvent.Reset(); // 阻塞线程

        Thread.Sleep(3000); // 主线程等待3秒
        Console.WriteLine("恢复线程...");
        _pauseEvent.Set(); // 唤醒线程

        thread.Join(); // 等待线程完成
    }

    static void DoWork()
    {
        while (true)
        {
            _pauseEvent.WaitOne(); // 如果信号量未设置,则线程会阻塞在此处
            Console.WriteLine("线程正在运行...");
            Thread.Sleep(500); // 模拟工作
        }
    }
}

十一、线程取消

可以通过标志变量取消线程。

示例(使用标志变量):

using System;
using System.Threading;

class Program
{
    private static bool _isRunning = true;
    private static readonly object _lock = new object(); // 用于同步的锁对象

    static void Main()
    {
        Thread thread = new Thread(DoWork);
        thread.Start();

        Thread.Sleep(2000); // 主线程等待2秒

        lock (_lock) // 使用锁保护对 _isRunning 的写操作
        {
            _isRunning = false; // 停止线程
        }

        Console.WriteLine("主线程已通知线程停止...");
    }

    static void DoWork()
    {
        while (true)
        {
            bool shouldRun;
            lock (_lock) // 使用锁保护对 _isRunning 的读操作
            {
                shouldRun = _isRunning;
            }

            if (!shouldRun)
            {
                Console.WriteLine("线程已停止...");
                break;
            }

            Console.WriteLine("线程正在运行...");
            Thread.Sleep(500); // 模拟工作
        }
    }
}

十二、线程安全与同步

多线程环境下,多个线程可能同时访问共享资源,导致数据不一致或竞争条件。为了解决这些问题,可以使用以下同步机制:

  • lock 关键字: 提供简单的互斥锁。
  • Monitor 类: 提供更灵活的锁定机制。
  • Mutex 类: 跨进程的互斥锁。
  • Semaphore 类: 控制对资源的访问数量。
  • AutoResetEvent 和 ManualResetEvent: 用于线程间的信号通信。

示例:使用 lock 实现线程安全

using System;
using System.Threading;

class Program
{
    private static object _lock = new object();
    private static int _counter = 0;

    static void Main(string[] args)
    {
        Thread t1 = new Thread(IncrementCounter);
        Thread t2 = new Thread(IncrementCounter);

        t1.Start();
        t2.Start();

        t1.Join();
        t2.Join();

        Console.WriteLine("Final Counter Value: " + _counter);
    }

    static void IncrementCounter()
    {
        for (int i = 0; i < 1000; i++)
        {
            lock (_lock)
            {
                _counter++;
            }
        }
    }
}

十三、注意事项

  • 避免滥用线程:每个线程都需要分配一定的栈空间(默认情况下是 1MB),因此创建大量线程会导致内存使用量迅速增加,并可能导致性能问题,建议使用线程池(ThreadPool 或 Task)来管理线程。
  • 避免使用过时方法:如 Abort()、Suspend() 和 Resume(),这些方法可能导致不可预测的行为。
  • 异常处理:线程中的异常不会传播到主线程,需在每个线程中单独捕获和处理。

 

 

 

你可能感兴趣的:(#,多线程编程,c#,多线程编程)