C# Multithreading (6): Automatic Thread Notification

2020年4月19日 66点热度 0人点赞 0条评论
内容目录

To recap, in the earlier sections on lock and Monitor, we learned about thread locks, in Mutex we learned about process synchronization, and in Semaphore we studied resource pool limits.

In this article, we will learn about the AutoRestEvent class used in C# for sending thread notifications.

AutoRestEvent Class

Used to send notifications from one thread to another.

The Microsoft documentation describes it as: Represents a thread synchronization event that automatically resets after a waiting thread receives a signal.

It has only one constructor:

The parameter in the constructor is used to set the signal state.

| Constructor | Description |
|--------------|-------------|
| AutoResetEvent(Boolean) | Initializes a new instance of the AutoResetEvent class with a Boolean value indicating whether to set the initial state to signaled. |

Terrible machine translation.

Common Methods

What does the AutoResetEvent class do and what does the constructor's parameter do? No need to rush, let’s first look at the commonly used methods of this class:

| Method | Description |
|--------|-------------|
| Close() | Releases all resources used by the current WaitHandle. |
| Reset() | Sets the event state to non-signaled, causing threads to block. |
| Set() | Sets the event state to signaled, allowing one or more waiting threads to proceed. |
| WaitOne() | Blocks the current thread until the current WaitHandle receives a signal. |
| WaitOne(Int32) | Blocks the current thread until the current WaitHandle receives a signal or a time interval (in milliseconds) specified by a 32-bit signed integer elapses. |
| WaitOne(Int32, Boolean) | Blocks the current thread until the current WaitHandle receives a signal or a time interval specified by a 32-bit signed integer elapses, and specifies whether to exit the synchronization domain before waiting. |
| WaitOne(TimeSpan) | Blocks the current thread until the current instance receives a signal, while specifying a time interval as a TimeSpan. |
| WaitOne(TimeSpan, Boolean) | Blocks the current thread until the current instance receives a signal, specifying a time interval as a TimeSpan, and whether to exit the synchronization domain before waiting. |

A Simple Example

Let’s write a program like this:

Create a thread that can perform tasks in multiple stages; after completing each stage, it needs to pause and wait for a notification from a child thread before proceeding to the next execution step.

.WaitOne() is used to wait for another thread to send a notification;

.Set() is used to notify the thread, putting AutoResetEvent into a signaled state;

.Reset() is used to reset the AutoResetEvent state;

    class Program
    {
        // Thread notification
        private static AutoResetEvent resetEvent = new AutoResetEvent(false);

        static void Main(string[] args)
        {
            // Create a thread
            new Thread(DoOne).Start();

            // Continuously send signals to another thread
            while (true)
            {
                Console.ReadKey();
                resetEvent.Set();           // Send notification, set signaled state
            }
        }

        public static void DoOne()
        {
            Console.WriteLine("Waiting, please send a signal to allow me to run");

            // Wait for other threads to send a signal
            resetEvent.WaitOne();

            Console.WriteLine("\n     Signal received, continuing execution");
            for (int i = 0; i 

Explanation

The AutoResetEvent object has signaled and non-signaled states. Set() sets the signaled state, while Reset() resets to the non-signaled state.

The signaled state can be understood as a signal that has been notified; the non-signaled state indicates that the signal has not been notified.

Note that the signaled and non-signaled states refer to the state of the AutoResetEvent, not the state of the thread.

It is noteworthy that if the AutoResetEvent is already in the signaled state, calling WaitOne() on a thread will not have any effect unless Reset() is called.

The parameter in the constructor is precisely for setting this state. true represents the signaled state while false represents the non-signaled state. If you use new AutoResetEvent(true);, the thread will not need to wait for a signal initially.

After using the type, you should release it explicitly by calling Close()/Dispose() or using using. Of course, you can also simply exit the program.

It is worth mentioning that if Set() is called multiple times with very short intervals, the second Set() may become ineffective (not work) if the first Set() has yet to be processed (sending a signal takes time).

A More Complex Example

Let’s design a program:

  • Two threads start in a blocked state;
  • Thread One can set Thread Two to continue running and then block itself;
  • Thread Two can set One to continue running and then block itself;

file

The program code is as follows (after running, please set the keyboard to English input state and then press a key):

    class Program
    {
        // Control the first thread
        // The first thread starts with the AutoResetEvent in a signaled state, no need to wait for a signal
        private static AutoResetEvent oneResetEvent = new AutoResetEvent(true);

        // Control the second thread
        // The second thread starts with the AutoResetEvent in a non-signaled state, needs to wait for a signal
        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① Press a key, and I will let DoTwo run");
                Console.ReadKey();
                twoResetEvent.Set();
                oneResetEvent.Reset();
                // Wait for DoTwo() to send me a signal
                oneResetEvent.WaitOne();

                Console.ForegroundColor = ConsoleColor.Green;
                Console.WriteLine("\n     DoOne() is executing");
                Console.ForegroundColor = ConsoleColor.White;
            }
        }

        public static void DoTwo()
        {
            while (true)
            {
                Thread.Sleep(TimeSpan.FromSeconds(1));

                // Wait for DoOne() to send me a signal
                twoResetEvent.WaitOne();

                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.WriteLine("\n     DoTwo() is executing");
                Console.ForegroundColor = ConsoleColor.White;

                Console.WriteLine("\n② Press a key, and I will let DoOne run");
                Console.ReadKey();
                oneResetEvent.Set();
                twoResetEvent.Reset();
            }
        }
    }

线程通知

Explanation

The two threads have the functionality of blocking themselves and releasing the other thread from blocking.

To understand this, we can reference a scene from the movie "The Best Partnership."

DoOne and DoTwo alternate breathing; they cannot control their own breathing but can determine when the other can breathe.

If you help me, I can help you, and then we can continue breathing.

file

Of course, WaitOne() can also set a waiting time. If the bald guy (DoOne) is being difficult and doesn’t let King Kong (DoTwo) breathe, King Kong can forcefully adjust the balance and breathe after waiting for a certain amount of time.

AutoResetEvent is also suitable for thread synchronization.

Additionally, when one thread uses WaitOne() and the other thread uses Set() to send a notification, the AutoResetEvent object will automatically revert to a non-signaled state; there's no need for the thread to use Reset().

痴者工良

高级程序员劝退师

文章评论