c parallel tasks multiple optimizations share of asynchronous delegation

  • 2020-05-30 20:59:21
  • OfStack

Encountered a problem with multithreaded task optimization, now resolved, share as follows.

Suppose there are four tasks:

Task 1: login verification (CheckUser)

Task 2: get data from Web service after successful validation (GetDataFromWeb)

Task 3: get data from the database after validation (GetDatFromDb)

Task 4: execute 1 method with 2, 3 data (StartProcess)

One of the more clumsy methods (my original method, named method 1) is to directly open a thread and execute 4 tasks in sequence:


new Thread(delegate
                {
                    CheckUser();
                    GetDatFromDb();// Get data from the database 
                    GetDataFromWeb();//web Service acquisition data 
                    StartProcess();// perform 4
                }).Start();

But close analysis of the demand that we will find the task 2 and task 3 did not have the difference, in fact both, there is no correlation, but the execution of task 4 need to task 2 and 3 have been completed as a condition, so we can open another two threads used to perform the task 2 and task 3, when both are completed, perform a task 4.

Two global variables are used here to represent the state of task 2 and task 3. Three threads are used to execute tasks 2, 3, and 4, respectively. Among them, task 41 listens for the state of global variables in a loop to ensure that it is not executed until both 2 and 3 are finished.

This is called method 2:


private static volatile bool _m2;// task 2 The mark of a 
 private static volatile bool _m3;// task 3 The mark of a 
 private static void Main(string[] args)
        {
            new Thread(delegate
                {
                    CheckUser();
                    new Thread(delegate
                        {
                             GetDatFromDb();// Get data from the database 
                             _m2 = true;// Mark position: true
                        }).Start();
                    new Thread(delegate
                        {
                            GetDataFromWeb();//web Service acquisition data 
                            _m3 = true;// Mark position: true
                        }).Start();
                    new Thread(delegate
                        {
                            while (!(_m3 && _m2))// Judgment task 2 and 3 Whether the execution has been completed 
                            {
                                Thread.Sleep(100);
                            }
                            StartProcess();// Perform a task 4
                            _m2 = true;
                        }).Start();
                }).Start();
          }

Code above is basically can achieve the anticipated goal, but because of using the two global variables, although here will not involve the issue of synchronization conflicts, but always feel very not trust, and when we need to do extension, for example, before a mission 4 we also need to load data in file (GetDataFromFile), then we must add a global flag bits, seem a bit of trouble.

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


Thread.Join  methods 
 Continue to implement the standard  COM  and  SendMessage  During message pump processing, the calling thread is blocked until a thread terminates. 

Simply put, join is a blocking method. Create thread 2 inside thread 1, call thread 2's Join method, and thread 1 will be blocked until thread 2 finishes executing. In the example above, the threads of task 2 and task 3 are created within task 4, and the Join method of the threads of task 2 and task 3 is invoked to block task 4, and the execution of task 4 is not continued until task 2 and task 3 are finished. This is called method 3, and the 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 acquisition data 
                                });
                            task2.Start();
                            task3.Start();
                            task2.Join();// task 2 blocking 
                            task3.Join();// task 3 blocking 
                            StartProcess(); // Perform a task 4
                        }).Start();
                }).Start();
        }


This eliminates the need for any flag bits. This is the ideal solution.

There is another solution that USES EventWaitHandle


EventWaitHandle  Class allows threads to communicate with each other by signaling and waiting for signals. The event wait handle (event) can be released by sending a corresponding signal 1 A waiting handle for one or more waiting threads. Once signaled, the event wait handle can be reset either manually or automatically 

In simple terms, this is an advanced version of method 2, which USES EventWaitHandle to control state rather than While loop listening, but still requires two global EventWaitHandle objects. This method is denoted as method 4, and the code is as follows


private static EventWaitHandle eventWait1 = new EventWaitHandle(false, EventResetMode.AutoReset);// Initialization state false;
        private static EventWaitHandle eventWait2 = new EventWaitHandle(false, EventResetMode.AutoReset);// Initialization state false;
        private static void Main(string[] args)
        {
            new Thread(delegate
                {
                    CheckUser();
                     new Thread(delegate
                    {
                        GetDatFromDb(); // Get data from the database 
                        eventWait1.Set(); // Mark position: true
                    }).Start();
                   new Thread(delegate
                    {
                        GetDataFromWeb(); //web Service acquisition data 
                        eventWait2.Set(); // Mark position: true
                    }).Start();
                    new Thread(delegate
                        {
                            eventWait1.WaitOne();// task 2 Block, wait 
                            eventWait2.WaitOne();// task 3 Block, wait 
                            StartProcess(); // Perform a task 4
                        }).Start();
                }).Start();
        }

The above three optimization scheme, in fact, the core thought is one kind of, is through open three threads respectively 2, 3, 4 missions, including task 4 is blocked (while circulation, eventWait. WaitOne, thread join), when the blocking after lifting, continue to perform a task 4. In other words, task 4 is actually 1 waiting for task 2 and task 3 to be completed. So, is there a way for task 2 and task 3 to proactively notify task 4? That is, after task 2 and task 3 are completed, actively execute task 4.

Methods of course: asynchronous delegate + callback function


private static object obj = new object();
        private static volatile bool _m2;// task 2 The mark of a 
        private static volatile bool _m3;// task 3 The mark of a 
        private static void Main(string[] args)
        {
            CheckUser(); // The first 1 step   Verify the user 
            Action step2 = delegate
            {
                GetDatFromDb(); // Get data from the database 
                _m2 = true; // Mark position: true
            };
            Action step3 = delegate
            {
                GetDataFromWeb(); //web Service acquisition data 
                _m3 = true; // Mark position: true
            };
            step2.BeginInvoke(delegate
            {
                if (_m2 && _m3) // Judge by the flag bit 2 3 Are they all done 
                {
                    lock (obj)// lock 
                    {
                        _m2 = false;
                        if (_m3)//2 Heavy validation   Prevent both from entering at the same time 
                            StartProcess(); // perform 4
                    }
                }
            }, null);
            step3.BeginInvoke(delegate
            {
                if (_m2 && _m3) // Judge by the flag bit 2 3 Are they all done 
                {
                    lock (obj)
                    {
                        _m3 = false;
                        if (_m2)
                            StartProcess(); // perform 4
                    }
                }
            }, null);
        }

Let's talk about the code. First, the delegate objects step2 and step3 of task 2 and task 3 are created by means of a delegate. The asynchronous invocation method BegInvoke that executes both delegates. To execute BegInvoke, a new thread is created to execute the step2 and step3 methods, and a callback function is specified when BeginInvoke is executed


delegate
            {
                if (_m2 && _m3) // Judge by the flag bit 2 3 Are they all done 
                {
                    lock (obj)
                    {
                        _m3 = false;
                        if (_m2)
                            StartProcess(); // perform 4
                    }
                }
            }

This function is called after the step2 and step3 threads have finished executing. Here again, I use the flag bit to determine whether step2 and step3 have been run, and to prevent a special situation: "step2 and step3 are running for almost the same amount of time, and they will pass through if (_m2) at the same time & & _m3) judge, and then execute StartProcess twice. "the lock lock is added here, and the flag bit is reset +2 judge inside the lock lock (refer to the singleton mode dual locking principle here), to ensure that StarProcess will only execute once.

In this way, a parallel task of active notification mode is implemented. However, this implementation method is too cumbersome compared with method 2, especially when it comes to concurrent processing, which is not very practical for individuals.

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


Related articles: