在多线程编程中,线程之间的同步是一个非常重要的环节。为了确保多个线程能够按照预期的顺序执行,或者在某些条件满足后再继续运行,我们需要一些机制来控制线程的执行流程。其中,`ManualResetEvent` 是 .NET 框架中提供的一种常用的同步基元对象,它在实现线程间的通信和协调方面有着广泛的应用。
一、ManualResetEvent 简介
`ManualResetEvent` 是 `System.Threading` 命名空间中的一个类,它用于控制线程的等待与唤醒。它的核心功能是让一个或多个线程进入等待状态,直到另一个线程调用其 `Set()` 方法来释放这些等待的线程。
与 `AutoResetEvent` 不同的是,`ManualResetEvent` 在调用 `Set()` 后,会一直保持“已设置”状态,直到显式调用 `Reset()` 方法将其重置为“未设置”状态。因此,所有等待该事件的线程都会被同时唤醒。
二、基本使用方法
1. 创建 ManualResetEvent 实例
```csharp
ManualResetEvent manualEvent = new ManualResetEvent(false);
```
构造函数的参数表示初始状态。`false` 表示事件未触发,线程在调用 `WaitOne()` 时会被阻塞;`true` 则表示事件已经触发,调用 `WaitOne()` 的线程将立即返回。
2. 线程等待事件
```csharp
manualEvent.WaitOne();
```
此方法会使当前线程进入等待状态,直到事件被设置为“已触发”。
3. 触发事件
```csharp
manualEvent.Set();
```
调用此方法后,所有正在等待的线程将被释放,并继续执行。
4. 重置事件
```csharp
manualEvent.Reset();
```
将事件状态恢复为“未触发”,后续调用 `WaitOne()` 的线程将再次被阻塞。
三、典型应用场景
1. 控制线程启动顺序
在需要按顺序启动多个线程的情况下,可以使用 `ManualResetEvent` 来控制每个线程的启动时机。
例如:
```csharp
ManualResetEvent startEvent = new ManualResetEvent(false);
Thread thread1 = new Thread(() =>
{
startEvent.WaitOne();
Console.WriteLine("Thread 1 is running.");
});
Thread thread2 = new Thread(() =>
{
startEvent.WaitOne();
Console.WriteLine("Thread 2 is running.");
});
thread1.Start();
thread2.Start();
// 允许线程开始执行
startEvent.Set();
```
2. 等待异步操作完成
在进行异步操作(如文件读写、网络请求)时,可以使用 `ManualResetEvent` 来通知主线程操作已完成。
```csharp
ManualResetEvent asyncDone = new ManualResetEvent(false);
Task.Run(() =>
{
// 模拟异步操作
Thread.Sleep(2000);
asyncDone.Set();
});
asyncDone.WaitOne(); // 主线程等待异步操作完成
Console.WriteLine("Async operation completed.");
```
四、注意事项
- 避免死锁:在使用 `ManualResetEvent` 时,要确保所有等待线程最终都能被正确唤醒,否则可能导致死锁。
- 资源释放:使用完 `ManualResetEvent` 后,建议调用 `Dispose()` 方法释放资源,尤其是在长时间运行的应用中。
- 线程安全:`ManualResetEvent` 是线程安全的,可以在多个线程之间共享使用。
五、总结
`ManualResetEvent` 是一种简单但功能强大的线程同步工具,适用于需要控制线程执行顺序、等待特定事件发生等场景。通过合理地使用它,可以有效提升多线程程序的稳定性和可维护性。掌握其基本用法和适用场景,对于开发高性能、高并发的 .NET 应用具有重要意义。