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

ManualResetEventSet() 为打开,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

AutoResetEventSet()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 。