Parallel programming and tasks

Keywords: C# Programming less

Preface

In the previous article, we focused on parallel programming, and in this section we continue with task-related knowledge.For better control of parallel operations, we can use the Task class in System.Threading.Tasks.The first step is to understand what a task is - a task represents a unit of work that is about to be completed, either running in a separate thread or starting up synchronously (waiting for the main thread to invoke).Why use tasks?- Tasks not only gain an abstraction layer (the unit of work to be completed), but also have better and more control over the underlying threads (the running of tasks).

Tasks using thread pools

We talked about using tasks to better control the underlying threads.When it comes to -- thread pools, which provide a pool of background threads.Thread pools manage threads independently, increase or decrease the number of threads as needed.Return to the thread pool using the completed thread.Let's look at the Create Task below:

Let's look at several ways to create tasks:

1. Use the instantiated TaskFactory class, and then use its StartNew method to start the task.

2. Use Task's static Factory to access the TaskFactory and then call the StartNew method to start the task.Similar to the first, but less comprehensive control over plant creation.

3. Use Task's constructor to instantiate the Task object to specify the creation task, and then start the task with the Start() method.

4. Use the Task.Run method to start the task immediately.

Let's look at the differences and similarities between the tasks created by the above methods and see the code:

        private static object _lock = new object();
     public
static void TaskMethond(object item) { lock (_lock) { Console.WriteLine(item?.ToString()); Console.WriteLine($"task Id: {Task.CurrentId?.ToString() ?? "No task running"}\t thread Id: {Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine($"Is it a thread in the thread pool:{Thread.CurrentThread.IsThreadPoolThread}"); Console.WriteLine($"Is it a background thread:{Thread.CurrentThread.IsBackground}"); Console.WriteLine(); } } #region Task Creation public static void TaskCreateRun() { var taskFactory = new TaskFactory(); var task1 = taskFactory.StartNew(TaskMethond, "Use instantiation TaskFactory"); var task2 = Task.Factory.StartNew(TaskMethond, "Use Task Static call Factory"); var task3 = new Task(TaskMethond, "Use Task Constructor instantiation"); task3.Start(); var task4 = Task.Run(() => TaskMethond("Use Task.Run")); } #endregion

Looking at the results of the code run, we found that no matter which method you use to create tasks, they are threads in the thread pool you've used.

Tasks with separate threads

Tasks, of course, are not necessarily run using threads in the thread pool, but can also use other threads.If the task is going to run for a long time, consider using a separate thread whenever possible (TaskCreationOptions.LongRunning), in which case the thread is not managed by the thread pool.Let's look at tasks that run without threads in the thread pool and with separate threads.

        #region Run the task on a separate thread
        public static void OnlyThreadRun()
        {
            var task1 = new Task(TaskMethond, "Run the task on a separate thread", TaskCreationOptions.LongRunning);
            task1.Start();
        }
        #endregion

Looking at its results, it's still a background thread, but it's not a thread in the thread pool being used.

Use synchronization tasks

Tasks can also run synchronously, using the same thread as the calling thread. Let's look at using the RunSynchronoushly method in the Task class to achieve synchronization.

 

        #region Synchronize Tasks

        public static void TaskRunSynchronoushly()
        {
            TaskMethond("Main Thread Call");
            var task1 = new Task(TaskMethond, "Task Synchronization Call");
            task1.RunSynchronously();
        }
        #endregion

 

 

Looking at the results of the run, we found that first we invoked the TaskMethond method with no task and used thread 1. Then when we created a Task instance to run the TaskMethond method, the task id was 1, but the thread still used main thread 1.(

Continuous Tasks

In a task, we can specify that when one task is completed, another should start immediately.It's like a task that should continue processing after it's finished.But after the failure we should do some work.

We can use the ContinueWith() method to define the use of continuous tasks, to indicate that a task should start after another task, or to specify that a task should start after success or start after failure (TaskContinuationOptions).

        #region Continuous Tasks
        public static void TaskOne()
        {
            Console.WriteLine($"task{Task.CurrentId},Method name:{System.Reflection.MethodBase.GetCurrentMethod().Name }start-up");
            Task.Delay(1000).Wait();
            Console.WriteLine($"task{Task.CurrentId},Method name:{System.Reflection.MethodBase.GetCurrentMethod().Name }End");
        }
        public static void TaskTwo(Task task)
        {
            Console.WriteLine($"task{task.Id}And over");
            Console.WriteLine($"The task that starts now is the task{Task.CurrentId},Method name:{System.Reflection.MethodBase.GetCurrentMethod().Name  }  ");
            Console.WriteLine($"Task Processing");
            Task.Delay(1000).Wait();
        }
        public static void TaskThree(Task task)
        {
            Console.WriteLine($"task{task.Id}And over");
            Console.WriteLine($"The task that starts now is the task{Task.CurrentId}.Method name:{System.Reflection.MethodBase.GetCurrentMethod().Name } ");
            Console.WriteLine($"Task Processing");
            Task.Delay(1000).Wait();
        }
        public static void ContinueTask()
        {
            Task task1 = new Task(TaskOne);
            Task task2 = task1.ContinueWith(TaskTwo, TaskContinuationOptions.OnlyOnRanToCompletion);//Continue task when completed
            Task task3 = task1.ContinueWith(TaskThree, TaskContinuationOptions.OnlyOnFaulted);//Continue task with unhandled exception
            task1.Start();
        }
        #endregion

Let's see what the code says is to start running TaskOne(), then run TaskTwo(Task task) when the task is complete, and then run TaskThree(Task task) if the task fails.We see that TaskOne() was run and then TaskTwo(Task task) was run successfully, avoiding TaskThree(Task task), so we can use ContinueWith to perform continuous tasks and TaskContinuationOptions to control task operation.

Task Hierarchy - Parent-Child Hierarchy

Here, by taking advantage of the continuity of tasks, I can start another task immediately after the end of one task, and tasks can also form a hierarchy.For example, when a task is started, a parent-child hierarchy is formed.Here's one example of this.

 

        #region Hierarchy of Tasks - Parent-Child Hierarchy
        public static void ChildTask()
        {
            Console.WriteLine("Currently running subtasks, open");
            Task.Delay(5000).Wait();
            Console.WriteLine("End of Subtask Run");
        }

        public static void ParentTask()
        {
            Console.WriteLine("Parent Task Open");
            var child = new Task(ChildTask);
            child.Start();
            Task.Delay(1000).Wait();
            Console.WriteLine("Parent Task End");
        }

        public static void ParentAndChildTask()
        {
            var parent = new Task(ParentTask);
            parent.Start();
            Task.Delay(2000).Wait();
            Console.WriteLine($"Status of parent task :{parent.Status}");
            Task.Delay(4000).Wait();
            Console.WriteLine($"Status of parent task:{parent.Status}");
        }
        #endregion

 

 

Waiting Task

In the.Net asynchronous programming described in the previous question, we talked about WhenAll, which handles multiple asynchronous methods.Here we continue with the extension points WhenAll () and WaitAll (), which are waiting for the tasks passed to them to be completed.However, the WaitAll() method blocks the calling task until all tasks are completed, and WhenAll() returns a task so that async keys can be used to wait for results.Tasks will not be blocked.Corresponding to this are WaitAny() and WhenAn().There is also the Task.Delay() method that we've always used to wait for tasks, specifying the number of milliseconds to wait before this method puts them back.

Next let's look at this ValueTask wait type (structure), which has no overhead for objects in the heap relative to the Task class.In general, the overhead of the Task type can be ignored, but in some special cases, such as when a method is called thousands of times.In this case, ValueTask becomes applicable.Let's look at the case below where using ValueTask returns a value directly from its constructor in five seconds.If the time is not within five seconds, use the method that actually gets the data.Then we compare it with the Task method.Here we take a test comparison of 100,000 data.

        #region Waiting Task
        private static DateTime _time;
        private static List<string> _data;
        public static async ValueTask<List<string>> GetStringDicAsync()
        {
            if (_time >= DateTime.Now.AddSeconds(-5))
            {
                return await new ValueTask<List<string>>(_data);
            }

            else
            {
                (_data, _time) = await GetData();
                return _data; 
            }
        }
        public static Task<(List<string> data, DateTime time)> GetData() => 
            Task.FromResult(
                (Enumerable.Range(0, 10).Select(x => $"itemString{x}").ToList(), DateTime.Now));

        public static async Task<List<string>> GetStringList()
        {
            (_data, _time) = await GetData();
            return _data;
        }
        #endregion
        static async Task  Main(string[] args)
        { 
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            Console.WriteLine("ValueTask start");
            for (int i = 0; i < 100000; i++)
            {
               var itemList= await GetStringDicAsync(); 
            }
            Console.WriteLine("ValueTask End");
            Console.WriteLine($"ValueTask Time consuming:{stopwatch.ElapsedMilliseconds}");

            Console.WriteLine();
            Console.WriteLine();

            stopwatch.Restart();
            Console.WriteLine("Task start");
            for (int i = 0; i < 100000; i++)
            {
                var itemList = await GetStringList(); 
            }
            Console.WriteLine("Task End");
            Console.WriteLine($"Task Time consuming:{stopwatch.ElapsedMilliseconds}");
            Console.ReadLine();
        }

Looking at the results, the time difference between running Task and ValueTask is enormous.So in some special cases, using ValueTask may be more appropriate.

summary

Today we introduce some knowledge concepts about tasks.In conjunction with the previous article, let's comb the relationships among some tasks, threads, multithreading, asynchronous, synchronous, concurrent, and parallel tasks.

First of all, let's see that the tasks and tasks we are learning in this chapter are a unit of work that will be completed. Who will complete them?This task is run by a thread.So what about multithreading?Multithreading should be a design concept for switching threads.Multiple threads can run multiple tasks, but in concurrency.Only one program can run at a time.It's just that the speed of switching between threads makes it look like multiple programs are running at the same time.In fact, microscopically speaking, only one program is running concurrently at any point in time, but the thread switching speed is fast.So how do I get to a very fast switching speed?This requires asynchronization.You don't need to wait for the thread to complete its results before continuing with other thread tasks.That is to say, you can wait.Implement A to run without waiting for results, then switch to B to continue running.This makes the switch very fast.Let's take a closer look at multithreaded switching threads as a way to achieve asynchronization.Synchronization occurs when there is asynchronization, which eliminates the need for multithreading.Wait until the last task is finished anyway.Continue running using the last thread.This is all about concurrency.So parallel?Parallelism can be said to allow multiple programs to run in one time, either microscopically or macroscopically.Concurrency is when multiple programs run on one processor, but parallel tasks run on multiple processors.For example, to achieve four tasks in parallel, we need at least four logical processing cores to work together to get there.

 Project Source Address

Among the easiest things in the world, procrastination is the least painful.Resilience is a key element of success. Knock on the door long enough and loud enough to wake people up.

Welcome to scan the QR code below to learn more with me

 

Posted by santhosh_89 on Thu, 07 Nov 2019 18:27:06 -0800