【C#基础】轻松理解AutoResetEvent 和 ManualResetEvent

彻底理解AutoResetEvent

AutoResetEvent 是一个线程同步原语,用于在多线程环境中协调线程的执行顺序。它是.NET Framework提供的一种同步机制,用于线程间的通信和协作。

AutoResetEvent 维护了一个状态标志,可以处于有信号或无信号两种状态之一。线程可以通过等待信号或设置信号来控制自己的执行。

AutoResetEvent 具有两个主要方法:WaitOne 和 Set。以下是这些方法的功能和用法:

1. WaitOne:
   - 当线程调用 WaitOne 方法时,它会进入等待状态,直到接收到信号。
   - 如果 AutoResetEvent 处于无信号状态,WaitOne 方法将阻塞线程的执行。
   - 如果 AutoResetEvent 处于有信号状态,WaitOne 方法将消耗该信号,并使 AutoResetEvent 重新进入无信号状态。
   - 可以通过 WaitOne 的重载方法指定超时时间,在超过指定时间后,线程将继续执行而不等待信号。

2. Set:
   - 当线程调用 Set 方法时,它会将 AutoResetEvent 的状态设置为有信号。
   - 如果有线程正在等待信号,它将被唤醒并开始执行。
   - 如果没有线程在等待信号,调用 Set 方法也会将 AutoResetEvent 的状态设置为有信号,但不会有任何其他影响。
   - 即使多次调用 Set 方法,AutoResetEvent 也只会保持有信号状态一次,直到被消耗。

AutoResetEvent 的工作方式可以类比于一个门闩或红绿灯。线程可以通过等待门闩打开(WaitOne)来阻塞自己的执行,而其他线程则可以通过设置门闩打开(Set)来唤醒等待的线程。

AutoResetEvent 在多线程编程中非常有用,特别是在需要控制线程的执行顺序或实现线程间的协作时。它提供了一种简单而有效的方式来同步线程的操作,并确保线程按照期望的顺序执行。

 举个例子

假设有两个线程,一个线程负责生产物品,另一个线程负责消费物品。这两个线程需要进行同步,以确保在生产者生成物品后,消费者才能消费物品。

使用 AutoResetEvent 可以很方便地实现这种同步。下面是一个简单的示例代码:

using System;
using System.Threading;

class ProgramesetEvent producerEvent = new AutoResetEvent(false);
    static AutoResetEvent consumerEvent = new AutoResetEvent(true);

    static int item = 0;

    static void Main()
    {
        Thread p
{
    static AutoRonsumerThread = new Thread(ConsumeItems);

        producerroducerThread = new Thread(ProduceItems);
        Thread cThread.Start();
        consumerThread.Start();

        producerThread.Join();
        consumerThread.Join();

        Console.WriteLine("Finished");
    }

    static void ProduceItems()
    {
        for (int i = 0; i < 5; i++)
        {
            consumerEvent.WaitOne(); // 等待消费者消费物品

            // 生产物品
            item = i;
            Console.WriteLine("Produced item: " + item);

            producerEvent.Set(); // 发送信号给消费者
        }
    }

    static void ConsumeItems()
    {
        for (int i = 0; i < 5; i++)
        {
            producerEvent.WaitOne(); // 等待生产者生产物品

            // 消费物品
            Console.WriteLine("Consumed item: " + item);

            consumerEvent.Set(); // 发送信号给生产者
        }
    }
}

在这个示例中,有一个共享的 `item` 变量表示当前要生产或消费的物品。`producerEvent` 控制生产者线程的执行,而 `consumerEvent` 控制消费者线程的执行。

生产者线程通过调用 `consumerEvent.WaitOne()` 进入等待状态,直到消费者线程消费完物品。然后,生产者线程生成一个新的物品,将其存储在 `item` 变量中,并调用 `producerEvent.Set()` 发送信号给消费者线程。

消费者线程通过调用 `producerEvent.WaitOne()` 进入等待状态,直到生产者线程生成物品。然后,消费者线程从 `item` 变量中获取物品并进行消费,并调用 `consumerEvent.Set()` 发送信号给生产者线程。

通过这种方式,生产者和消费者线程可以交替地生产和消费物品,并且彼此之间保持同步。AutoResetEvent 的使用确保了生产者和消费者线程之间的正确顺序执行,避免了竞态条件和数据不一致的问题。

new AutoResetEvent 传入的参数是干嘛用的呢?

在 AutoResetEvent 的构造函数中传入的参数是一个布尔值,用于指定初始的信号状态。这个参数被称为 `initialState`。

- 如果 `initialState` 为 `true`,则 AutoResetEvent 的初始状态为有信号状态。也就是说,线程在等待时不会被阻塞,而是可以直接执行。此时,调用 `WaitOne` 方法时,如果 AutoResetEvent 处于无信号状态,则线程将立即继续执行,不会等待信号。

- 如果 `initialState` 为 `false`,则 AutoResetEvent 的初始状态为无信号状态。也就是说,线程在等待时会被阻塞,直到收到信号才能继续执行。此时,调用 `WaitOne` 方法时,如果 AutoResetEvent 处于无信号状态,则线程将被阻塞,直到调用 `Set` 方法将其置为有信号状态。

可以根据实际需求来选择初始的信号状态。如果希望线程在开始时能够继续执行而不被阻塞,可以使用有信号状态;如果希望线程在开始时等待信号才能执行,可以使用无信号状态。

例如,在生产者-消费者示例中,可以使用无信号状态作为初始状态,以确保生产者线程在开始时等待消费者线程的信号才能开始生产物品。这样可以避免生产者在没有消费者消费物品时不断生成新物品。

AutoResetEvent 和 ManualResetEvent 有啥区别

AutoResetEvent 和 ManualResetEvent 都是线程同步原语,用于在多线程环境中协调线程的执行顺序,但它们之间有一些重要的区别。

1. 触发方式:
   - AutoResetEvent:在调用 `Set` 方法后,只有一个等待线程会被唤醒并继续执行,然后 AutoResetEvent 会自动恢复为无信号状态。即每次调用 `Set` 方法只唤醒一个等待线程。
   - ManualResetEvent:在调用 `Set` 方法后,所有等待线程都会被唤醒并继续执行,直到显式调用 `Reset` 方法将 ManualResetEvent 设置回无信号状态为止。即每次调用 `Set` 方法会唤醒所有等待线程。

2. 状态重置:
   - AutoResetEvent:自动重置为无信号状态。即当一个线程等待信号并被唤醒后,AutoResetEvent 会自动将自身重置为无信号状态,以便下一个等待的线程能够继续等待。
   - ManualResetEvent:需要显式调用 `Reset` 方法将其重置为无信号状态。即 ManualResetEvent 会保持有信号状态,直到调用 `Reset` 方法将其置回无信号状态。

3. 等待方式:
   - AutoResetEvent:在调用 `WaitOne` 方法时,如果 AutoResetEvent 处于无信号状态,线程会被阻塞直到接收到信号。如果 AutoResetEvent 处于有信号状态,线程会消耗该信号并继续执行。
   - ManualResetEvent:在调用 `WaitOne` 方法时,如果 ManualResetEvent 处于无信号状态,线程会被阻塞直到接收到信号。即使 ManualResetEvent 处于有信号状态,线程也会消耗该信号并继续执行,而不会自动将其重置为无信号状态。

根据这些区别,可以选择使用 AutoResetEvent 或 ManualResetEvent 来适应不同的线程同步需求。

- 如果需要每次只唤醒一个等待线程,并且希望线程在接收到信号后自动重置为无信号状态,可以使用 AutoResetEvent。这对于一些排队任务的场景很有用。
- 如果需要唤醒所有等待线程,并且希望线程在接收到信号后保持有信号状态,直到显式调用 Reset 方法将其重置为无信号状态,可以使用 ManualResetEvent。这对于一些广播通知的场景很有用。

需要根据具体的应用场景和线程同步需求来选择使用哪种同步原语。

小结

信号同步的原理就是等待信号,有信号状态直接过,无信号就等。怎么等?通过调用WaitOnce等。Set设置信号为有信号,ReSet设置状态为无信号。AutoResetEvent就是等完之后会自动变成无信号!与之相反是ManualResetEven。

Manual翻译过来就是手动。需要显式调用 `Reset` 方法将其重置为无信号状态。

还有一个关键的问题是多个一个的区别!

我们可以的通过 同一个AutoResetEvent 对象 在不同的线程里调用很多次WaitOnce。

这样就会又很多个线程等待同一个信号,由于AutoResetEvent是自动恢复无信号的,所以只有一个等待线程会被唤醒并继续执行,那这一个线程肯定是最先调用WaitOnce的地方,因为一旦调用了WaitOnce并通过了,AutoResetEvent自动恢复无信号。其他的当然得继续等咯。

而 ManualResetEven 是手动的,所以能唤醒所有的。

猜你喜欢

转载自blog.csdn.net/songhuangong123/article/details/131591253
今日推荐