马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
一、引言
嘿,各位 C# 编程的小同伴们!在多线程编程的世界里,我们常常会遇到需要让线程临时 “休息” 一下的情况。这时候,Sleep()和Wait()这两个方法就如同我们的得力助手,随时预备为我们服务。但是,你真的相识它们吗?它们之间又有着怎样的区别呢?本日,就让我们一起深入探讨C#中Sleep()和Wait()的奥秘,看看在不同的场景下,究竟该如何选择,才气让我们的线程 “休息” 得恰到好处。
二、理论底子
2.1 Sleep () 原理剖析
Sleep()是System.Threading.Thread类的一个静态方法 ,它的主要作用是让当前正在执行的线程停息一段时间。其停息的时长由传入的参数决定,单元为毫秒。比方,当我们调用Thread.Sleep(2000)时,当火线程就会停息 2 秒钟。在这 2 秒内,该线程不会执行任何代码,CPU 也不会分配时间片给它。需要注意的是,Sleep()方法并不会释放当火线程所持有的锁。假设我们有一个多线程程序,其中一个线程在获取了某个对象的锁之后调用了Sleep()方法,那么在它就寝的这段时间里,其他线程是无法获取该锁的,即便它们实验访问被该锁保护的资源,也只能处于等待状态。
2.2 Wait () 原理详解
Wait()是Object类的实例方法,它用于线程间的通讯和协作。通常情况下,Wait()方法需要在同步代码块(lock语句块)或同步方法中使用,并且它依靠于一个对象锁。当一个线程调用Wait()方法后,它会释放当前持有的对象锁,并进入等待状态。此时,该线程会被放入到对象的等待队列中,直到其他线程调用了同一个对象的Notify()或NotifyAll()方法,才有可能被唤醒。比方,在一个生产者 - 消费者模子中,当消费者线程发现队列中没有数据时,可以调用Wait()方法进入等待状态,同时释放锁,让生产者线程可以或许将数据放入队列。当生产者线程完成数据生产后,调用Notify()或NotifyAll()方法唤醒等待的消费者线程。
三、深入对比
3.1 根本用法差异
Sleep()方法的使用非常简单直接,只需在需要停息线程的地方调用Thread.Sleep(毫秒数) 即可。比如,我们想要让当火线程停息 3 秒钟,可以这样写:
- using System;
- using System.Threading;
- class Program
- {
- static void Main()
- {
- Console.WriteLine("线程开始执行");
- Thread.Sleep(3000);
- Console.WriteLine("线程暂停3秒后继续执行");
- }
- }
复制代码 在上述代码中,当执行到Thread.Sleep(3000)时,当火线程会停息 3000 毫秒(即 3 秒),然后再继续执行后面的代码。
而Wait()方法的使用则相对复杂一些,它需要与lock语句共同使用,并且通常在一个循环中调用,以确保在合适的条件下等待。比方:
- using System;
- using System.Threading;
- class Program
- {
- private static object _lockObject = new object();
- private static bool _flag = false;
- static void Main()
- {
- new Thread(() =>
- {
- lock (_lockObject)
- {
- while (!_flag)
- {
- Console.WriteLine("等待条件满足...");
- Monitor.Wait(_lockObject);
- }
- Console.WriteLine("条件满足,继续执行");
- }
- }).Start();
- new Thread(() =>
- {
- Thread.Sleep(2000);
- lock (_lockObject)
- {
- _flag = true;
- Console.WriteLine("设置条件为真,并通知等待线程");
- Monitor.Pulse(_lockObject);
- }
- }).Start();
- }
- }
复制代码 在这段代码中,第一个线程在获取锁后,查抄_flag是否为true,如果不是则调用Monitor.Wait(_lockObject)进入等待状态,并释放锁。第二个线程在 2 秒后获取锁,设置_flag为true,然后调用Monitor.Pulse(_lockObject)通知等待的线程。被通知的线程重新获取锁后,继续执行后续代码。
3.2 锁处置惩罚的不同
Sleep()方法在停息线程时,不会释放当火线程所持有的锁。这意味着,如果一个线程在持有锁的情况下调用了Sleep(),其他线程想要获取该锁就必须等待该线程就寝结束并释放锁。假设有一个银行转账的场景,我们用代码模拟如下:
- using System;
- using System.Threading;
- class BankAccount
- {
- private int balance = 1000;
- private object lockObject = new object();
- public void Withdraw(int amount)
- {
- lock (lockObject)
- {
- Console.WriteLine("开始取款操作,当前余额: " + balance);
- if (amount <= balance)
- {
- Thread.Sleep(2000);
- balance -= amount;
- Console.WriteLine("取款成功,剩余余额: " + balance);
- }
- else
- {
- Console.WriteLine("余额不足,取款失败");
- }
- }
- }
- }
- class Program
- {
- static void Main()
- {
- BankAccount account = new BankAccount();
- new Thread(() => account.Withdraw(500)).Start();
- new Thread(() => account.Withdraw(300)).Start();
- }
- }
复制代码 在这个例子中,Withdraw方法使用了锁来保证线程安全。当一个线程进入Withdraw方法并获取锁后,调用Thread.Sleep(2000)模拟业务处置惩罚的耗时操作,在这 2 秒内,锁不会被释放,其他线程无法进入该方法,只能等待。
与之相反,Wait()方法在调用时会释放当火线程持有的锁,使得其他线程有机会获取该锁并执行同步代码块中的内容。当等待的线程被唤醒时,它会重新实验获取锁,只有获取到锁后才会继续执行。下面是一个简单的生产者 - 消费者模子示例:
- using System;
- using System.Collections.Generic;
- using System.Threading;
- class ProducerConsumer
- {
- private Queue<int> queue = new Queue<int>();
- private object lockObject = new object();
- private const int MaxQueueSize = 5;
- public void Produce(int item)
- {
- lock (lockObject)
- {
- while (queue.Count >= MaxQueueSize)
- {
- Console.WriteLine("队列已满,生产者等待");
- Monitor.Wait(lockObject);
- }
- queue.Enqueue(item);
- Console.WriteLine("生产了: " + item);
- Monitor.PulseAll(lockObject);
- }
- }
- public void Consume()
- {
- lock (lockObject)
- {
- while (queue.Count == 0)
- {
- Console.WriteLine("队列已空,消费者等待");
- Monitor.Wait(lockObject);
- }
- int item = queue.Dequeue();
- Console.WriteLine("消费了: " + item);
- Monitor.PulseAll(lockObject);
- }
- }
- }
- class Program
- {
- static void Main()
- {
- ProducerConsumer pc = new ProducerConsumer();
- new Thread(() =>
- {
- for (int i = 1; i <= 10; i++)
- {
- pc.Produce(i);
- Thread.Sleep(1000);
- }
- }).Start();
- new Thread(() =>
- {
- for (int i = 1; i <= 10; i++)
- {
- pc.Consume();
- Thread.Sleep(1500);
- }
- }).Start();
- }
- }
复制代码 在这个例子中,当队列满时,生产者线程调用Monitor.Wait(lockObject)释放锁并进入等待状态;当队列空时,消费者线程调用Monitor.Wait(lockObject)释放锁并进入等待状态。当生产者生产了数据大概消费者消费了数据后,会调用Monitor.PulseAll(lockObject)通知等待的线程。
3.3 线程状态厘革
当线程调用Sleep()方法后,会进入壅闭状态(Blocked),在指定的时间内,该线程不会参与 CPU 的调理,也不会执行任何代码。当就寝的时间结束后,线程会从壅闭状态变化为就绪状态(Ready),等待 CPU 分配时间片来继续执行。比方,在一个游戏开发中,我们需要控制某个动画的播放速度,假设每帧动画需要停息 50 毫秒,代码如下:
- using System;
- using System.Threading;
- class Animation
- {
- public void Play()
- {
- for (int i = 0; i < 100; i++)
- {
- Console.WriteLine("播放第 " + (i + 1) + " 帧动画");
- Thread.Sleep(50);
- }
- }
- }
- class Program
- {
- static void Main()
- {
- Animation animation = new Animation();
- new Thread(animation.Play).Start();
- }
- }
复制代码 在这个例子中,每播放一帧动画,线程就会调用Thread.Sleep(50)进入壅闭状态 50 毫秒,50 毫秒后进入就绪状态,等待 CPU 分配时间片继续播放下一帧。
而线程调用Wait()方法后,会进入等待状态(Waiting),并且释放持有的锁。此时,该线程会被放入对象的等待队列中,直到被其他线程通过Notify()或NotifyAll()方法唤醒。唤醒后,线程会从等待状态变化为就绪状态,竞争锁资源,获取到锁后才会继续执行。比方,在一个多线程协作的文件处置惩罚系统中,假设有一个主线程负责读取文件内容,一个辅助线程负责对读取的内容举行解析,代码如下:
- using System;
- using System.IO;
- using System.Threading;
- class FileProcessor
- {
- private object lockObject = new object();
- private string fileContent;
- private bool isContentReady = false;
- public void ReadFile(string filePath)
- {
- lock (lockObject)
- {
- using (StreamReader reader = new StreamReader(filePath))
- {
- fileContent = reader.ReadToEnd();
- }
- isContentReady = true;
- Console.WriteLine("文件读取完成,通知解析线程");
- Monitor.Pulse(lockObject);
- }
- }
- public void ParseContent()
- {
- lock (lockObject)
- {
- while (!isContentReady)
- {
- Console.WriteLine("等待文件内容读取完成");
- Monitor.Wait(lockObject);
- }
- Console.WriteLine("开始解析文件内容: " + fileContent);
- }
- }
- }
- class Program
- {
- static void Main()
- {
- FileProcessor processor = new FileProcessor();
- new Thread(() => processor.ReadFile("test.txt")).Start();
- new Thread(processor.ParseContent).Start();
- }
- }
复制代码 在这个例子中,解析线程调用Monitor.Wait(lockObject)进入等待状态,当读取线程读取完文件内容后,调用Monitor.Pulse(lockObject)唤醒解析线程,解析线程从等待状态变为就绪状态,竞争锁资源,获取到锁后开始解析文件内容。
3.4 响应停止能力
Sleep()方法在执行期间,线程处于壅闭状态,它不会响应停止请求。如果在一个线程调用Sleep()期间,另一个线程对其发出停止请求,该线程会忽略这个停止信号,继续就寝直到指定的时间结束。比方,我们有一个定时任务线程,每隔一段时间执行一次任务,在任务执行过程中,不希望被停止干扰,代码如下:
- using System;
- using System.Threading;
- class ScheduledTask
- {
- public void Run()
- {
- while (true)
- {
- Console.WriteLine("开始执行定时任务");
- Thread.Sleep(5000);
- Console.WriteLine("定时任务执行完成");
- }
- }
- }
- class Program
- {
- static void Main()
- {
- Thread taskThread = new Thread(new ScheduledTask().Run);
- taskThread.Start();
- Thread.Sleep(3000);
- taskThread.Interrupt();
- Console.WriteLine("尝试中断定时任务线程");
- }
- }
复制代码 在这个例子中,定时任务线程调用Thread.Sleep(5000)进入就寝状态,3 秒后主线程实验停止它,但定时任务线程会忽略停止请求,继续就寝直到 5 秒结束。
相比之下,Wait()方法可以响应停止请求。当一个线程在等待状态时,如果被其他线程停止,它会抛出InterruptedException非常,从而可以在捕获非常时举行相应的处置惩罚。比方,在一个多线程的网络通讯程序中,一个线程在等待服务器响应时,可能需要在某些情况下提前停止等待,代码如下:
- using System;
- using System.Threading;
- class NetworkClient
- {
- private object lockObject = new object();
- private bool isResponseReceived = false;
- public void WaitForResponse()
- {
- lock (lockObject)
- {
- try
- {
- while (!isResponseReceived)
- {
- Console.WriteLine("等待服务器响应");
- Monitor.Wait(lockObject);
- }
- Console.WriteLine("收到服务器响应");
- }
- catch (InterruptedException e)
- {
- Console.WriteLine("等待被中断: " + e.Message);
- }
- }
- }
- public void SimulateResponse()
- {
- lock (lockObject)
- {
- isResponseReceived = true;
- Console.WriteLine("模拟服务器响应,通知等待线程");
- Monitor.Pulse(lockObject);
- }
- }
- }
- class Program
- {
- static void Main()
- {
- NetworkClient client = new NetworkClient();
- Thread waitThread = new Thread(client.WaitForResponse);
- waitThread.Start();
- Thread.Sleep(2000);
- waitThread.Interrupt();
- Console.WriteLine("中断等待线程");
- }
- }
复制代码 在这个例子中,等待线程在调用Monitor.Wait(lockObject)进入等待状态后,2 秒后被主线程停止,会捕获InterruptedException非常并举行相应的处置惩罚。
四、适用场景
4.1 Sleep () 适用场景
- 定时任务执行:在需要定时执行某项任务的场景中,Sleep()方法能发挥重要作用。比方,一个监控系统需要每隔一段时间(如 5 分钟)查抄一次服务器的状态,我们可以使用Sleep()方法来控制查抄的时间间隔。示例代码如下:
- using System;
- using System.Threading;
- class ServerMonitor
- {
- public void StartMonitoring()
- {
- while (true)
- {
- Console.WriteLine("开始检查服务器状态");
- // 模拟检查服务器状态的操作
- // ......
- Thread.Sleep(5 * 60 * 1000);
- }
- }
- }
- class Program
- {
- static void Main()
- {
- ServerMonitor monitor = new ServerMonitor();
- new Thread(monitor.StartMonitoring).Start();
- }
- }
复制代码 在这段代码中,Thread.Sleep(5 * 60 * 1000)使得线程每 5 分钟执行一次服务器状态查抄操作。
- 模拟耽误结果:在一些需要模拟耽误的场景,如网络请求耽误、动画结果耽误等,Sleep()方法可以简单地实现耽误功能。比如,在一个简单的登录验证功能中,为了模拟网络耽误,我们可以在验证逻辑执行后添加一个短暂的耽误,让用户感受到更真实的网络请求过程。示例代码如下:
- using System;
- using System.Threading;
- class LoginSystem
- {
- public void Login(string username, string password)
- {
- Console.WriteLine("正在验证用户名和密码...");
- // 模拟验证逻辑
- bool isValid = ValidateCredentials(username, password);
- if (isValid)
- {
- Thread.Sleep(2000);
- Console.WriteLine("登录成功");
- }
- else
- {
- Thread.Sleep(2000);
- Console.WriteLine("用户名或密码错误");
- }
- }
- private bool ValidateCredentials(string username, string password)
- {
- // 简单的验证逻辑示例
- return username == "admin" && password == "123456";
- }
- }
- class Program
- {
- static void Main()
- {
- LoginSystem loginSystem = new LoginSystem();
- loginSystem.Login("admin", "123456");
- }
- }
复制代码 在上述代码中,无论登录成功与否,都会通过Thread.Sleep(2000)模拟 2 秒的耽误,加强用户体验。
4.2 Wait () 适用场景
- 生产者 - 消费者模子:这是Wait()方法最典型的应用场景。在该模子中,生产者线程负责生产数据并将其放入共享队列中,消费者线程则从队列中取出数据举行处置惩罚。当队列已满时,生产者线程需要等待;当队列已空时,消费者线程需要等待。通过Wait()和Notify()(或NotifyAll())方法,生产者和消费者线程可以实现高效的协作。比方,我们有一个生产包子和消费包子的场景,代码如下:
- using System;
- using System.Collections.Generic;
- using System.Threading;
- class BaoziShop
- {
- private Queue<string> baoziQueue = new Queue<string>();
- private object lockObject = new object();
- private const int MaxQueueSize = 10;
- public void ProduceBaozi()
- {
- while (true)
- {
- lock (lockObject)
- {
- while (baoziQueue.Count >= MaxQueueSize)
- {
- Console.WriteLine("包子队列已满,生产者等待");
- Monitor.Wait(lockObject);
- }
- string baozi = "新生产的包子";
- baoziQueue.Enqueue(baozi);
- Console.WriteLine("生产了一个包子,当前队列中有 " + baoziQueue.Count + " 个包子");
- Monitor.PulseAll(lockObject);
- }
- }
- }
- public void ConsumeBaozi()
- {
- while (true)
- {
- lock (lockObject)
- {
- while (baoziQueue.Count == 0)
- {
- Console.WriteLine("包子队列已空,消费者等待");
- Monitor.Wait(lockObject);
- }
- string baozi = baoziQueue.Dequeue();
- Console.WriteLine("消费了一个包子,当前队列中有 " + baoziQueue.Count + " 个包子");
- Monitor.PulseAll(lockObject);
- }
- }
- }
- }
- class Program
- {
- static void Main()
- {
- BaoziShop shop = new BaoziShop();
- new Thread(shop.ProduceBaozi).Start();
- new Thread(shop.ConsumeBaozi).Start();
- }
- }
复制代码 在这个例子中,当包子队列满时,生产者线程调用Monitor.Wait(lockObject)进入等待状态并释放锁;当包子队列空时,消费者线程调用Monitor.Wait(lockObject)进入等待状态并释放锁。当生产者生产了包子大概消费者消费了包子后,会调用Monitor.PulseAll(lockObject)通知等待的线程。
- 线程间资源竞争与协作:在多个线程需要访问共享资源,并且需要根据资源的状态举行协作的场景中,Wait()方法能很好地和谐线程间的执行次序。比方,在一个多线程的文件读取和处置惩罚系统中,有一个线程负责读取文件内容,另一个线程负责对读取的内容举行解析。当读取线程还未完成读取时,解析线程需要等待;当读取线程完成读取后,通知解析线程开始工作。示例代码如下:
- using System;
- using System.IO;
- using System.Threading;
- class FileHandler
- {
- private object lockObject = new object();
- private string fileContent;
- private bool isContentReady = false;
- public void ReadFile(string filePath)
- {
- lock (lockObject)
- {
- using (StreamReader reader = new StreamReader(filePath))
- {
- fileContent = reader.ReadToEnd();
- }
- isContentReady = true;
- Console.WriteLine("文件读取完成,通知解析线程");
- Monitor.Pulse(lockObject);
- }
- }
- public void ParseContent()
- {
- lock (lockObject)
- {
- while (!isContentReady)
- {
- Console.WriteLine("等待文件内容读取完成");
- Monitor.Wait(lockObject);
- }
- Console.WriteLine("开始解析文件内容: " + fileContent);
- }
- }
- }
- class Program
- {
- static void Main()
- {
- FileHandler handler = new FileHandler();
- new Thread(() => handler.ReadFile("test.txt")).Start();
- new Thread(handler.ParseContent).Start();
- }
- }
复制代码 在这段代码中,解析线程调用Monitor.Wait(lockObject)进入等待状态,直到读取线程完成文件读取并调用Monitor.Pulse(lockObject)通知它。
五、注意事项
5.1 Sleep () 使用注意
在使用Sleep()方法时,需要注意制止因设置过长的就寝时间导致程序假死或响应迟缓。比方,在一个图形用户界面(GUI)应用程序中,如果主线程调用了Sleep()方法且就寝时间较长,那么界面将无法响应用户的操作,如点击按钮、拖动窗口等,严峻影响用户体验 。
- using System;
- using System.Threading;
- using System.Windows.Forms;
- namespace SleepInGUI
- {
- public partial class Form1 : Form
- {
- public Form1()
- {
- InitializeComponent();
- }
- private void button1_Click(object sender, EventArgs e)
- {
- // 错误示范:在主线程中长时间Sleep
- Thread.Sleep(10000);
- MessageBox.Show("睡眠结束");
- }
- }
- }
复制代码 在上述代码中,当用户点击按钮时,主线程会进入 10 秒的就寝状态,在此期间,整个应用程序的界面将处于无响应状态。为了制止这种情况,可以考虑将耗时的操作放在新的线程中执行,而不是在主线程中调用Sleep()。
5.2 Wait () 使用注意
- 锁的正确管理:使用Wait()方法时,必须确保正确地获取和释放锁。如果在调用Wait()之前没有获取锁,大概在不适当的地方释放锁,都可能导致程序出现不可预测的结果,甚至死锁。比方:
- using System;
- using System.Threading;
- class DeadlockExample
- {
- private static object lockObject1 = new object();
- private static object lockObject2 = new object();
- public static void Thread1()
- {
- lock (lockObject1)
- {
- Console.WriteLine("Thread1 获取了 lockObject1");
- // 错误示范:在未获取lockObject2的情况下尝试等待
- Monitor.Wait(lockObject2);
- }
- }
- public static void Thread2()
- {
- lock (lockObject2)
- {
- Console.WriteLine("Thread2 获取了 lockObject2");
- // 错误示范:在未获取lockObject1的情况下尝试等待
- Monitor.Wait(lockObject1);
- }
- }
- }
- class Program
- {
- static void Main()
- {
- new Thread(DeadlockExample.Thread1).Start();
- new Thread(DeadlockExample.Thread2).Start();
- }
- }
复制代码 在这段代码中,Thread1和Thread2分别获取了不同的锁,然后实验在未获取的锁上调用Wait(),这将导致死锁。正确的做法是确保在调用Wait()之前,线程已经获取了对应的锁。
- 制止死锁:除了正确管理锁之外,还需要注意制止因线程间的循环等待而导致死锁。在复杂的多线程场景中,多个线程可能会相互依靠,形成循环等待的情况。比方,假设有三个线程A、B、C,它们分别持有锁lockA、lockB、lockC,并且A等待B释放lockB,B等待C释放lockC,C等待A释放lockA,这样就会形成死锁。为了制止这种情况,需要仔细设计线程的同步逻辑,确保不会出现循环等待的情况。
- 处置惩罚线程唤醒后的竞争问题:当一个线程被Notify()或NotifyAll()唤醒后,它会与其他线程竞争锁资源。在高并发的情况下,可能会出现竞争激烈的情况,导致性能降落。为了缓解这种情况,可以考虑使用一些并发控制的本领,如信号量(Semaphore)、互斥锁(Mutex)等,来控制线程的访问次序和并发度。比方,使用信号量来限制同时访问某个资源的线程数量:
- using System;
- using System.Threading;
- class ResourceAccess
- {
- private static Semaphore semaphore = new Semaphore(2, 2);
- private static object lockObject = new object();
- public static void AccessResource()
- {
- semaphore.WaitOne();
- lock (lockObject)
- {
- try
- {
- Console.WriteLine("线程进入资源访问区");
- Thread.Sleep(2000);
- Console.WriteLine("线程完成资源访问");
- }
- finally
- {
- semaphore.Release();
- }
- }
- }
- }
- class Program
- {
- static void Main()
- {
- for (int i = 0; i < 5; i++)
- {
- new Thread(ResourceAccess.AccessResource).Start();
- }
- }
- }
复制代码 在这个例子中,Semaphore被初始化为允许最多 2 个线程同时访问资源,通过WaitOne()和Release()方法来控制线程的进入和离开,从而制止了过多线程同时竞争资源的情况。
六、总结
在 C# 多线程编程领域,Sleep()和Wait()作为控制线程执行流程的重要本领,各自有着独特的功能和适用场景。Sleep()简单直接,适用于需要线程停息固定时长的场景,像定时任务、模拟耽误结果等,其不会释放锁的特性,在特定业务逻辑中能保证数据的一致性和操作的原子性。而Wait()则更偏重于线程间的协作与同步,通过释放锁,让多个线程能高效地访问共享资源,在生产者 - 消费者模子以及线程间资源竞争与协作场景中发挥着关键作用。
理解这两个方法的焦点区别,并根据实际需求合理运用,是编写高效、稳定多线程程序的关键。希望通过本文的探讨,能帮助大家在多线程编程的道路上更加得心应手,编写出更健壮、更具扩展性的代码。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |