总目录
前言
在C#中,AutoResetEvent
是一个用于线程同步的关键类,它位于 System.Threading
命名空间下。它的核心功能是通过信号机制控制线程的执行顺序,允许一个或多个线程等待某个信号后再继续运行。常用于需要线程间通信的场景。
一、核心概念
- 作用:
AutoResetEvent
是一种同步原语,用于在多线程环境中实现线程间的等待和通知- 通知等待的线程某个事件已发生(如资源就绪、操作完成)。
- 信号状态:
- 有信号(Signaled):线程调用
WaitOne()
不会被阻塞。 - 无信号(Non-signaled):线程调用
WaitOne()
会被阻塞,直到信号触发。
- 有信号(Signaled):线程调用
- 自动重置:
- 当一个等待线程被释放后,AutoResetEvent 会自动将状态重置为非信号状态(即非触发状态)。这意味着每次仅允许一个等待线程继续执行。
本文中所描述的 有信号状态、终止状态、触发状态 意义相同,都是同一种状态的不同名称
二、基本用法
1. 构造函数
var autoEvent = new AutoResetEvent(initialState: false); // 初始无信号/或称 未触发
initialState
:初始化是否为有信号状态(true
表示有信号/或称 已触发,则线程一开始是无需等待信号的)。
2. 关键方法
WaitOne()
:阻塞当前线程,直到事件变为有信号。可以指定超时时间。autoEvent.WaitOne(); // 阻塞直到信号触发
- 如果 AutoResetEvent 已经处于有信号状态,那么线程调用 WaitOne() 不会再起阻塞线程的作用。除非调用Reset() 。
Set()
:将事件设置为有信号状态(释放一个等待线程)。autoEvent.Set(); // 发送信号,唤醒一个等待线程
- 当调用 Set() 时,若有线程在等待,会释放一个线程并自动重置为无信号状态。
- 若调用 Set() 时没有线程等待,事件保持有信号状态,直到后续的 WaitOne() 调用自动消费信号并重置。
Reset()
:将事件设置为无信号状态autoEvent.Reset();
- 手动 Reset() 的用途:
- 强制将事件设置为无信号状态,无论当前是否有线程在等待。
- 确保下一轮的 WaitOne() 必须等待对方的信号,避免因事件残留信号导致逻辑错误。
- 手动 Reset() 的用途:
注意:
- 有信号和无信号状态指的是AutoResetEvent的状态,而不是线程的状态
- 如果多次调用 Set() 的时间间隔过短,如果第一次 Set() 还没有结束(信号发送需要处理时间),那么第二次 Set() 可能无效(不起作用)。
三、 示例
示例 1:线程等待主线程信号
using System.Threading;class Program
{static AutoResetEvent autoEvent = new AutoResetEvent(false);static void Main(){Thread worker = new Thread(DoWork);worker.Start();// 主线程触发信号Thread.Sleep(2000);Console.WriteLine("主线程发送信号");autoEvent.Set(); // 唤醒工作线程}static void DoWork(){Console.WriteLine("工作线程等待信号...");autoEvent.WaitOne(); // 阻塞直到信号到来Console.WriteLine("工作线程继续执行");}
}
输出
工作线程等待信号...
主线程发送信号
工作线程继续执行
示例 2:主线程等待工作线程信号
using System;
using System.Threading;class Program
{static AutoResetEvent _autoResetEvent = new AutoResetEvent(false);static void Main(string[] args){// 启动一个新线程Thread t = new Thread(Work);t.Start();Console.WriteLine("主线程等待...");// 主线程等待_autoResetEvent.WaitOne();Console.WriteLine("主线程继续执行");}static void Work(){Console.WriteLine("工作线程正在工作...");// 模拟一些工作Thread.Sleep(1000);Console.WriteLine("工作完成,通知主线程");// 发送信号,允许等待的线程继续_autoResetEvent.Set();}
}
在这个例子中,主线程调用 _autoResetEvent.WaitOne(); 等待,而工作线程在完成任务后调用 _autoResetEvent.Set(); 通知主线程继续执行。
输出
主线程等待...
工作线程正在工作...
工作完成,通知主线程
主线程继续执行
示例3:线程执行控制
class Program
{// 控制第一个线程// 第一个线程开始时,AutoResetEvent 处于终止状态,无需等待信号private static AutoResetEvent oneResetEvent = new AutoResetEvent(true);// 控制第二个线程// 第二个线程开始时,AutoResetEvent 处于非终止状态,需要等待信号private static AutoResetEvent twoResetEvent = new AutoResetEvent(false);static void Main(string[] args){new Thread(DoOne).Start();new Thread(DoTwo).Start();Console.ReadKey();}public static void DoOne(){while (true){Console.WriteLine("\n① 按下任意键,我就让DoTwo运行");Console.ReadKey();twoResetEvent.Set();oneResetEvent.Reset();// 等待 DoTwo() 给我信号oneResetEvent.WaitOne();Console.ForegroundColor = ConsoleColor.Green;Console.WriteLine("\n DoOne() 执行");Console.ForegroundColor = ConsoleColor.White;}}public static void DoTwo(){while (true){Thread.Sleep(TimeSpan.FromSeconds(1));// 等待 DoOne() 给我信号twoResetEvent.WaitOne();Console.ForegroundColor = ConsoleColor.Yellow;Console.WriteLine("\n DoTwo() 执行");Console.ForegroundColor = ConsoleColor.White;Console.WriteLine("\n② 按下任意键,我就让DoOne运行");Console.ReadKey();oneResetEvent.Set();twoResetEvent.Reset();}}
}
代码分析
-
DoOne 线程流程
- 调用 twoResetEvent.Set():通知 DoTwo 可以运行。
- 手动调用 oneResetEvent.Reset():强制将 oneResetEvent 设为非终止状态。
- 调用 oneResetEvent.WaitOne():阻塞等待 DoTwo 的 Set()。
-
DoTwo 线程流程
- 调用 twoResetEvent.WaitOne():等待 DoOne 的信号。
- 执行完成后,调用 oneResetEvent.Set():通知 DoOne 继续。
- 手动调用 twoResetEvent.Reset():强制将 twoResetEvent 设为非终止状态。
-
关键点
- 手动 Reset() 的目的是立即重置事件状态,确保下一轮循环必须严格等待对方的信号。
- 若省略 Reset(),事件可能残留终止状态,导致 WaitOne() 直接通过,破坏交替逻辑。
- 必须保留 Reset():
- oneResetEvent.Reset() 确保 DoOne 必须等待 DoTwo 的信号。
- twoResetEvent.Reset() 确保 DoTwo 必须等待 DoOne 的信号。
- 若省略,残留信号会导致线程间同步失效,破坏交替执行的预期逻辑。
-
省略 Reset() 场景:省略 oneResetEvent.Reset()
- 假设 DoOne 未调用 oneResetEvent.Reset():
- DoOne 调用 twoResetEvent.Set(),通知 DoTwo 运行。
- DoOne 调用 oneResetEvent.WaitOne()。
- 若 oneResetEvent 仍为终止状态(例如,DoTwo 在上一轮已调用 Set()),WaitOne() 会直接通过,无需等待。
- 导致 DoOne 和 DoTwo 同时执行,破坏交替逻辑。
- 假设 DoOne 未调用 oneResetEvent.Reset():
四、高级用法
1. 超时等待
bool signaled = autoEvent.WaitOne(TimeSpan.FromSeconds(5)); // 最多等待5秒
if (!signaled)
{Console.WriteLine("等待超时");
}
2. 多线程竞争
// 多个线程等待同一个事件
for (int i = 0; i < 3; i++)
{new Thread(() => {autoEvent.WaitOne();Console.WriteLine($"线程 {Thread.CurrentThread.ManagedThreadId} 被唤醒");}).Start();
}// 需要调用 Set() 三次才能唤醒所有线程
autoEvent.Set(); // 唤醒第一个线程
autoEvent.Set(); // 唤醒第二个线程
autoEvent.Set(); // 唤醒第三个线程
5、 注意事项
- 资源释放:用完需调用
Dispose()
或使用using
块(AutoResetEvent
继承自WaitHandle
)。 - 竞态条件:确保
Set()
和WaitOne()
的调用顺序合理,避免死锁。 - 与 ManualResetEvent 的区别:
AutoResetEvent
自动重置信号,适合一次唤醒一个线程。ManualResetEvent
需手动调用Reset()
,适合广播多个线程。
六、替代方案
ManualResetEventSlim
:轻量级版本,性能更优。SemaphoreSlim
:控制并发访问数量。TaskCompletionSource
:基于任务的异步模式(TAP)。
结语
回到目录页:C#/.NET 知识汇总
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。
参考资料:
自动线程通知 AutoResetEvent