SoFunction
Updated on 2025-03-07

C# parallel task sharing multiple optimization solutions (asynchronous delegation)

I encountered a multi-threaded task optimization problem, and now it has been solved. I will share it as follows.

Suppose there are four tasks:

Task 1: Login Verification (CheckUser)

Task 2: Get data from the Web service after verification is successful (GetDataFromWeb)

Task 3: Get data from the database after verification is successful (GetDatFromDb)

Task 4: Use data from 2 and 3 to execute a method (StartProcess)

A relatively stupid method (my initial method is recorded as method 1) is to directly open a thread and execute four tasks in sequence:

Copy the codeThe code is as follows:

new Thread(delegate
                {
                    CheckUser();
GetDatFromDb();//Get data from the database
GetDataFromWeb();//web service gets data
StartProcess();//Execute 4
                }).Start();

However, we will find that there is no difference between task 2 and task 3 in sequence. In fact, the two are not related. However, the execution of task 4 requires that tasks 2 and 3 have been completed as conditions, so we can open two more threads to execute task 2 and task 3. After both are executed, task 4 is executed.

Here two global variables are used to represent the state of task 2 and task 3. Use three threads to execute tasks 2, 3, and 4 respectively, where task 4 keeps listening to the status of global variables in a loop to ensure that it is executed only after both 2 and 3 are executed.

This is written as Method 2:

Copy the codeThe code is as follows:

private static volatile bool _m2;//Flag bit of task 2
private static volatile bool _m3;//The flag bit of task 3
 private static void Main(string[] args)
        {
            new Thread(delegate
                {
                    CheckUser();
                    new Thread(delegate
                        {
GetDatFromDb();//Get data from the database
_m2 = true;//The flag position is true
                        }).Start();
                    new Thread(delegate
                        {
GetDataFromWeb();//web service gets data
_m3 = true;//The flag position is true
                        }).Start();
                    new Thread(delegate
                        {
While (!(_m3 && _m2))//Defend whether tasks 2 and 3 have been executed
                            {
                                (100);
                            }
StartProcess();//Execute Task 4
                            _m2 = true;
                        }).Start();
                }).Start();
          }

The above code can basically achieve the expected goal, but because of the help of two global variables, although there will be no synchronization conflicts here, I always feel very worried. Moreover, when we need to expand, for example, before executing task 4, we also need to load the data in the file (GetDataFromFile), then we have to add another global flag bit, which seems a bit troublesome.

In fact, the Thread class itself already has the perfect solution to this situation - join.

Copy the codeThe code is as follows:

method
During the process of continuing to perform standard COM and SendMessage message pumps, the calling thread is blocked until a thread terminates.

Simply put, join is a blocking method. If thread 2 is created in thread 1 and the Join method of thread 2 is called, then thread 1 will be blocked until thread 2 has been executed. To apply the above example, create a thread of task 2 and task 3 in task 4, call the Join method of task 2 and task 3 to block task 4, and do not continue to execute task 4 until task 2 and task 3 are executed. This is recorded as method 3, the code is as follows

Copy the codeThe code is as follows:

private static void Main(string[] args)
        {
            new Thread(delegate
                {
                    CheckUser();
                    new Thread(delegate
                        {
                            Thread task2 = new Thread(delegate
                                {
GetDatFromDb(); //Get data from the database
                                });
                            Thread task3 = new Thread(delegate
                                {
GetDataFromWeb(); //web service gets data
                                });
                            ();
                            ();
();//Task 2 blocking
();//Task 3 blocking
StartProcess(); //Execute Task 4
                        }).Start();
                }).Start();
        }


This way, no sign is needed. This is the ideal solution.

There is another solution, using EventWaitHandle

Copy the codeThe code is as follows:

The EventWaitHandle class allows threads to communicate with each other by sending signals and waiting signals. Event waiting handle (referred to as event) is the waiting handle of one or more waiting threads by issuing a corresponding signal. After the signal is issued, the event waiting handle can be reset manually or automatically.

Simply put, it is the advanced version of Method 2. It uses EventWaitHandle to control the state, and no longer uses While loop to listen, but there are still two global EventWaitHandle objects needed here. This method is called Method 4, the code is as follows

Copy the codeThe code is as follows:

private static EventWaitHandle eventWait1 = new EventWaitHandle(false, );//Initialization state false;
private static EventWaitHandle eventWait2 = new EventWaitHandle(false, );//Initialization state false;
        private static void Main(string[] args)
        {
            new Thread(delegate
                {
                    CheckUser();
                     new Thread(delegate
                    {
GetDatFromDb(); //Get data from the database
(); //The flag position is true
                    }).Start();
                   new Thread(delegate
                    {
GetDataFromWeb(); //web service gets data
(); //The flag position is true
                    }).Start();
                    new Thread(delegate
                        {
();//Task 2 blocks, waiting
();//Task 3 blocks, waiting
StartProcess(); //Execute Task 4
                        }).Start();
                }).Start();
        }

The above three optimization solutions are actually the same core idea. They all execute tasks 2, 3, and 4 by opening three threads, respectively. Among them, task 4 is blocked (while loop,), and when the blocking is released, task 4 continues to be executed. In other words, Task 4 is actually waiting for Task 2 and Task 3 to complete. So, is there a way to make Task 2 and Task 3 actively notify Task 4? That is, after tasks 2 and 3 are completed, task 4 is actively executed.

Of course the methods include: asynchronous delegation + callback function

Copy the codeThe code is as follows:

private static object obj = new object();
private static volatile bool _m2;//Flag bit of task 2
private static volatile bool _m3;//Flag bit of task 3

        private static void Main(string[] args)
        {
CheckUser(); //Step 1: Verify the user
            Action step2 = delegate
            {
GetDatFromDb(); //Get data from the database
_m2 = true; //The flag position is true
            };
            Action step3 = delegate
            {
GetDataFromWeb(); //web service gets data
_m3 = true; //The flag position is true
            };

            (delegate
            {
if (_m2 && _m3) //Judge whether all 2 3 has been completed by using the flag bit
                {
lock (obj)// Add lock
                    {
                        _m2 = false;
If (_m3)//Two-fold verification prevents both from entering at the same time
StartProcess(); //Execute 4
                    }
                }
            }, null);
            (delegate
            {
if (_m2 && _m3) //Judge whether all 2 3 has been completed by using the flag bit
                {
                    lock (obj)
                    {
                        _m3 = false;
                        if (_m2)
StartProcess(); //Execute 4
                    }
                }
            }, null);
        }

Explain the code. First, the delegate objects step2 and step3 of task 2 and task 3 are created in the delegate way. Execute the asynchronous call method BegInvoke for these two delegates. Execution of BegInvoke will create a new thread to execute the step2 and step3 methods. At the same time, a callback function is also specified when executing BeginInvoke.

Copy the codeThe code is as follows:

delegate
            {
if (_m2 && _m3) //Judge whether all 2 3 has been completed by using the flag bit
                {
                    lock (obj)
                    {
                        _m3 = false;
                        if (_m2)
StartProcess(); //Execute 4
                    }
                }
            }

This function will be called after the threads of step2 and step3 are executed. Here, I used the flag bits again to determine whether step2 and step3 have been run. At the same time, in order to prevent a special situation: "The execution time of step2 and step3 is almost equal, they will use if (_m2&&_m3) to perform two StartProcess" A lock lock is added here, and the flag bit is reset + double judgment in the lock lock (here you can refer to the double lock principle of singleton mode), to ensure that StarProcess will only be executed once.

In this way, a parallel task with active notification mode is implemented. However, this implementation method is too troublesome compared to Method 2, especially in terms of concurrency processing. I personally feel that it is not very practical.

In addition, you can also use the observer mode to achieve the effect of asynchronous delegate + callback function.