ManualResetEvent 与 AutoResetEvent 简介

ManualResetEvent 和 AutoResetEvent 是两种线程同步的方案。

他们可以通过调用 WaitOne() 方法阻塞当前线程,直到其他线程上调用了 Set() 方法。

例子

详细的使用方法如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
var manualResetEvent = new ManualResetEvent(false);

var task1 = new Task(() =>
{
Console.WriteLine("task1 : Before WaitOne()");
manualResetEvent.WaitOne();
Console.WriteLine("task1 : After WaitOne()");
});

var task2 = new Task(() =>
{
Console.WriteLine("task2 : Before Set()");
manualResetEvent.Set();
Console.WriteLine("task2 : After Set()");
});

task1.Start();
Thread.Sleep(1000);
task2.Start();

Task.WaitAll(task1, task2);
}
}
}

运行结果如下:

1
2
3
4
5
task1 : Before WaitOne()
task2 : Before Set()
task2 : After Set()
task1 : After WaitOne()
请按任意键继续. . .

使用方法

首先需要初始化一个 ManualResetEvent 或是 AutoResetEvent 。构造函数带一个参数,类型为 bool ,表示初始状态是否设置为终止。这个状态之后可以通过 Set() 和 Reset() 方法来改变。

换句话说,类似于 Event 中有一个开关,表示是否 WaitOne 时是否阻塞。

如果初始化为 true ,或是调用过 Set() ,那么这个开关就是打开的状态,当 Event 调用 WaitOne() 方法时,线程不会暂停,会继续执行下去。

而当初始化为 false ,或是调用过 Reset() 时,那么这个开关就是关闭的状态,当 Event 调用 WaitOne() 方法时线程会被阻塞,直到有其他线程通过 Set() 打开了开关。

ManualResetEvent 与 AutoResetEvent 的区别

ManualResetEvent

ManualResetEvent 的 Set() 为打开,Reset() 为关闭。一旦打开,所有阻塞在 WaitOne() 的线程都会继续执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
var manualResetEvent = new ManualResetEvent(false);

var task1 = new Task(() =>
{
Console.WriteLine("task1 : Before WaitOne()");
manualResetEvent.WaitOne();
Console.WriteLine("task1 : After WaitOne()");
});

var task2 = new Task(() =>
{
Console.WriteLine("task2 : Before WaitOne()");
manualResetEvent.WaitOne();
Console.WriteLine("task2 : After WaitOne()");
});

var task3 = new Task(() =>
{
Console.WriteLine("task3 : Before Set()");
manualResetEvent.Set();
Console.WriteLine("task3 : After Set()");
});

task1.Start();
task2.Start();
Thread.Sleep(1000);
task3.Start();

Task.WaitAll(task1, task2, task3);
}
}
}

运行结果:

1
2
3
4
5
6
7
task2 : Before WaitOne()
task1 : Before WaitOne()
task3 : Before Set()
task3 : After Set()
task1 : After WaitOne()
task2 : After WaitOne()
请按任意键继续. . .

AutoResetEvent

AutoResetEvent 的 Set() 和 Reset() 与 ManualResetEvent 一致。不同的是,每有一个阻塞在 WaitOne() 的线程由于开关打开而继续执行,都会自动回弹开关。(这也就是 AutoReset 的含义)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
var autoResetEvent = new AutoResetEvent(false);

var task1 = new Task(() =>
{
Console.WriteLine("task1 : Before WaitOne()");
autoResetEvent.WaitOne();
Console.WriteLine("task1 : After WaitOne()");
});

var task2 = new Task(() =>
{
Console.WriteLine("task2 : Before WaitOne()");
autoResetEvent.WaitOne();
Console.WriteLine("task2 : After WaitOne()");
});

var task3 = new Task(() =>
{
Console.WriteLine("task3 : Before Set()");
autoResetEvent.Set();
Console.WriteLine("task3 : After Set()");
});

task1.Start();
task2.Start();
Thread.Sleep(1000);
task3.Start();

Task.WaitAll(task1, task2, task3);
}
}
}

运行结果:

1
2
3
4
5
task1 : Before WaitOne()
task2 : Before WaitOne()
task3 : Before Set()
task3 : After Set()
task1 : After WaitOne()

保持阻塞,无法继续执行。
原因是 task1 被唤醒的同时关闭了开关, task2 无法通过。

结论

需要唤醒的线程只有一个时,两种没有区别。

会有多个线程同时等待,而每次只希望唤醒一个线程时,用 AutoResetEvent ,

希望一次唤醒所有线程永久可以通过时,用 ManualResetEvent 。