C# Task source code reading (1)

Posted by james_cwy on Sat, 13 Jul 2019 03:16:50 +0200

Usually, we often use Task in our development. Many of the later. net versions are related to Task. For example, asyn,await has Task. We seldom pay attention to Thread. Task has brought us a lot of convenience. We pay less attention to the process of execution and more attention to logic. But sometimes, there are applications. And we have to consider the operation status of task, such as whether the task is successful or not, whether there is an exception. I often hear people say that task is executed in thread pool, so let's see what task is doing and what he does when he executes it.

You can do it from Here You can see the source code of Task, or you can download it directly from reference code.

Let's look at this code first.

public class Task : IThreadPoolWorkItem, IAsyncResult, IDisposable
    {
        [ThreadStatic]
        internal static Task t_currentTask;  // The currently executing task.
        [ThreadStatic]
        private static StackGuard t_stackGuard;  // The stack guard object for this thread

        internal static int s_taskIdCounter; //static counter used to generate unique task IDs
        private readonly static TaskFactory s_factory = new TaskFactory();

        private volatile int m_taskId; // this task's unique ID. initialized only if it is ever requested

        internal object m_action;    // The body of the task.  Might be Action<object>, Action<TState> or Action.  Or possibly a Func.
        // If m_action is set to null it will indicate that we operate in the
        // "externally triggered completion" mode, which is exclusively meant 
        // for the signalling Task<TResult> (aka. promise). In this mode,
        // we don't call InnerInvoke() in response to a Wait(), but simply wait on
        // the completion event which will be set when the Future class calls Finish().
        // But the event would now be signalled if Cancel() is called
}

Look at the interface inherited by the Task class. IThreadPoolItem is related to thread pool, IAsyncResult is related to the drop-back of asynchronous execution. I won't say much about it here.

Then we see a field t_current Task, which is static, pointing to its task. You don't know if there is any doubt about it. Why do you design this way? In fact, this kind of design exists in many places of. net, such as HttpContext and so on. The basic feature is a Current. It's a bit like the singleton mode, but it's initialized at first, and there's a little more that you can replace at any time and inject into your own definition. It's totally ok to use him as a singleton. Note that the access modifier here is internal static.

Then t_stackGuard,s_taskIdCounter, as its name implies, is not introduced too much.

Here's the s_factory note that it's a static and access modifier. Of course, if I use factory mode, I rarely use the current factory inside the class. Of course I have to do this for the finished product I want to produce someday.

Next comes the more important field m_action, the executor. Do you remember how to execute so-called functions in the assembly, push a push b call xxxx? A and B are parameters respectively, XXXX is the jump address to execute the code, and the parameters are usually passed through stack. It's put directly into object s here on. net, and the comments are clearly written as delegates. But for a function, its executor is the address of call.

Next, let's look at the fields below.

        internal object m_stateObject; // A state object that can be optionally supplied, passed to action.
        internal TaskScheduler m_taskScheduler; // The task scheduler this task runs under. 

        internal readonly Task m_parent; // A task's parent, or null if parent-less.


        internal volatile int m_stateFlags;

m_stateObject guesses until it works.

Here is another field m_task Scheduler, which is particularly important in the execution process. We usually use taskScheduler on windows platform. When it comes to taskScheduler, the function is to arrange reasonable taskexecution at a reasonable time. In fact, it is an execution manager. Of course, we have similar work in sql server development tools. job execution, we also have to choose the execution plan. Of course, the m_task Scheduler here may have its own meaning, both task schedulers. Of course, task Scheduler by default is somewhat different from the tools we just mentioned. Of course, you have the impression that it is used to schedule task. As for how to schedule, each has its own plan.

m_stateFlags status flag field. A Task execution, of course, I would like to until his current status, start, end, so this is also easy to understand. There are many states in Thread itself.

Keep looking at the code

   public void Start()
        {
            Start(TaskScheduler.Current);
        }
public void Start(TaskScheduler scheduler)
        {
            // Read the volatile m_stateFlags field once and cache it for subsequent operations
            int flags = m_stateFlags;

            // Need to check this before (m_action == null) because completed tasks will
            // set m_action to null.  We would want to know if this is the reason that m_action == null.
            if (IsCompletedMethod(flags))
            {
                throw new InvalidOperationException(Environment.GetResourceString("Task_Start_TaskCompleted"));
            }

            if (scheduler == null)
            {
                throw new ArgumentNullException("scheduler");
            }

            var options = OptionsMethod(flags);
            if ((options & (TaskCreationOptions)InternalTaskOptions.PromiseTask) != 0)
            {
                throw new InvalidOperationException(Environment.GetResourceString("Task_Start_Promise"));
            }
            if ((options & (TaskCreationOptions)InternalTaskOptions.ContinuationTask) != 0)
            {
                throw new InvalidOperationException(Environment.GetResourceString("Task_Start_ContinuationTask"));
            }

            // Make sure that Task only gets started once.  Or else throw an exception.
            if (Interlocked.CompareExchange(ref m_taskScheduler, scheduler, null) != null)
            {
                throw new InvalidOperationException(Environment.GetResourceString("Task_Start_AlreadyStarted"));
            }

            ScheduleAndStart(true);
        }

 

We usually use the start method. He will pass in a Task Scheduler by default. Let's look at the following method. Finally, we call the Schedule AndStart method. Regardless of the previous validation, we focus on the execution process. To understand this, we must understand the Task Scheduler. Current.

What kind is it, what is its function? If we write Task Scheduler ourselves, what should we write and what functions should we accomplish?

We continue to find the TaskScheduler class from reference code. Let's focus on tracking Current first, regardless of the method.

 public static TaskScheduler Current 
        {
            get
            {
                TaskScheduler current = InternalCurrent;
                return current ?? TaskScheduler.Default;
            }
        }
 internal static TaskScheduler InternalCurrent
        {
            get
            {
                Task currentTask = Task.InternalCurrent;
                return ( (currentTask != null) 
                    && ((currentTask.CreationOptions & TaskCreationOptions.HideScheduler) == 0)
                    ) ? currentTask.ExecutingTaskScheduler : null;
            }
        }

 

By default, I continue to find the default attribute
public static TaskScheduler Default 
        {
            get
            {
                return s_defaultTaskScheduler;
            }
        }
   private static readonly TaskScheduler s_defaultTaskScheduler = new ThreadPoolTaskScheduler();

 

We tracked it step by step and finally found ThreadPool Task Scheduler, at which point it was finally possible for task to link threadpool.

Before we go back to implementing Schedule AndStart, let's look at it
  if (Interlocked.CompareExchange(ref m_taskScheduler, scheduler, null) != null)
 null judgment and the assignment of the object. This can be borrowed from normal code.



Topics: C# less Windows SQL Attribute