[C# task] TaskContinuationOptions bit enumeration

TaskContinuationOptions

[Flags,serializable]
public enum TaskContinuationOptions(
None=ooooo, //default
//The subtasks generated by the current task are scheduled to global tasks instead of local task queues.
PreferFairness=ox0001,
//proposal TaskScheduler Thread pools should be created whenever possible
LongRunning=ox0002,
//There is no parent-child relationship between tasks, but this enumeration item can realize the parent-child relationship between tasks:Will one Task And its father Task relation(Discussed later)
AttachedToParent=ox0004,
//The task trying to connect to this parent task will throw a InvalidOperationException
DenychildAttach=oxo008, //Force subtasks to use the default scheduler instead of the scheduler of the parent task Hidescheduler=Ox0010 //Unless predecessor(antecedent task)Complete, otherwise it is forbidden to continue the task(cancel) Lazycancellation=oxO020 //This flag indicates that you want to be executed by the thread performing the first task //Continuewith Mission. After the first task is completed,call //Continuewith Thread following execution ContinueWith task ExecuteSynchronously=0x80000, //These flags indicate when to run Continuewith task NotOnRanToCompletion =0x10000, NotOnFaulted=Ox20000, NotOnCanceled=0x40000, //These signs are a convenient combination of the above three signs OnlyonCanceled=NotOnRanToCompletion l NotOnFaulted, OnlyonFaulted= NotOnRanToCompletion I NotOnCanceled, OnlyonRanToCompletion = NotOnFaulted | NotonCanceled,

 TaskContinuationOptions.AttachedToParent use case


One or more Task objects created by a Task are top-level tasks by default, and they have nothing to do with the Task that created them. But taskcreationoptions AttachedToParent
Flag associates a Task with the Task that created it. As a result, the created Task (parent Task) is not considered finished unless all subtasks (and subtasks of subtasks) finish running. When the ContinueWith method is called to create a Task,
You can specify taskcontinuationoptions The attachedtoparent flag specifies the continuation task as a subtask (see the following example for this sentence)---- ckr via C# 4th Edition P625

/*As long as the tasks created in task a (a, b, c, including the tasks created by b's ContinueWith) are top-level tasks, only when a, b, c, etc. are setAttachedToParentThat's the subtask of A.
 * With taskcreationoptions Attachedtoparent or taskcontinuationoptions All tasks of the attachedtoparent attribute are attached to its parent task.
 * If the TaskCreationOptions of the parent task are not DenyChildAttach, the AttachedToParent of the child task will work.
 * The default TaskCreationOptions of the parent task listed below are None, so the AttachedToParent of the subtask works.
 * If the parent task has been completed, but the child task attached to the parent task has not been completed, then the parent task is in WaitingForChildrenToComplete.
 * Only when the subtasks and parent tasks attached to the parent task are completed, the status of the parent task will become rantocompletement.
 * RanToCompletion=[Subtask rantocompletement attached to parent task] + [parent task rantocompletement]
 * 
 * */
Task taskparent = new(() => {
    Task subtask1 = new(() => {
        Console.WriteLine("subtask1 Discord taskparent Association, etc subtask1 Execute after completion");
        Console.WriteLine("subtask1 Go to sleep, start 0.1s ");
        Thread.Sleep(100);
        Console.WriteLine("subtask1 I wake up ");
    }  );
    //ContinueWith Method, which is not attached to the parent task taskparent,Is a separate task, so the parent task does not need to wait for it to complete.
    Task subtask2 = subtask1.ContinueWith(task => {
        Console.WriteLine("subtask2 Discord subtask1 Association, etc subtask1 Execute after completion");
        Console.WriteLine("subtask2 Start execution, sleep 3 s ");

        Thread.Sleep(3000);
        Console.WriteLine("subtask2  I wake up ");
    });
    
    //ContinueWith Create subtasks as 
    Task subtask3 = subtask1.ContinueWith(task => {
        Console.WriteLine("subtask3  and subtask1 Association, etc subtask1 Execute after completion");
        Console.WriteLine("subtask3 Start execution, sleep 3 s ");
        Thread.Sleep(1000);
        Console.WriteLine("subtask3  I wake up ");
//It sets the taskparent attached to the parent task (not subtask1). If the TaskCreationOptions of the parent task are not denychildatch.

//Then it will work. The parent task will not change the status to after it is completed together RanToCompletion,If the parent task extraction is completed, the parent task status is during the waiting subtaskWaitingForChildrenToComplete
},TaskContinuationOptions.AttachedToParent); subtask1.Start(); Console.WriteLine(subtask1.CreationOptions); }); taskparent.Start();
while (!taskparent.IsCompleted) { Console.WriteLine(taskparent.Status); } Console.WriteLine(taskparent.Status); Console.Read();

 

 TaskCreationOptions.DenyChildAttach

(reject adoptive child) reject any subtask attached to it. Taskcreationoptions set in all its subtasks Attachedtoparent or taskcontinuationoptions The attachedtoparent attribute is not valid for him.

It will not wait for any subtasks. As long as its own tasks are completed, its state will become rantocompletement.

 

Task taskparent = new(() => {
    Task subtask1 = new(() => {
        Console.WriteLine("subtask1 Discord taskparent Association, etc subtask1 Execute after completion");
        Console.WriteLine("subtask1 Start execution, sleep 0.1s ");
        Thread.Sleep(100);
        Console.WriteLine("subtask1 I wake up ");
    }  );
    
    Task subtask2 = subtask1.ContinueWith(task => {
        Console.WriteLine("subtask2 Discord subtask1 Association, etc subtask1 Execute after completion");
        Console.WriteLine("subtask2 Start execution, sleep 3 s ");

        Thread.Sleep(3000);
        Console.WriteLine("subtask2  I wake up ");
    });
    //ContinueWith Method, which is attached to the parent task taskparent(no subtask1),If the parent task TaskCreationOptions Not for DenyChildAttach. Then he works.

    Task subtask3 = subtask1.ContinueWith(task => {
        Console.WriteLine("subtask3  and subtask1 Association, etc subtask1 Execute after completion");
        Console.WriteLine("subtask3 Start execution, sleep 3 s ");
        Thread.Sleep(1000);
        Console.WriteLine("subtask3  I wake up ");
    },TaskContinuationOptions.AttachedToParent);
    subtask1.Start();
    Console.WriteLine(subtask1.CreationOptions);
    //We won't wait until any subtasks are set TaskCreationOptions.AttachedToParent or TaskContinuationOptions.AttachedToParent attribute
}, TaskCreationOptions.DenyChildAttach);
 
taskparent.Start();
while (!taskparent.IsCompleted)
{

    Console.WriteLine(taskparent.Status);
  
}
Console.WriteLine(taskparent.Status);
Console.Read();

 TaskContinuationOptions.Hidescheduler

Force subtasks to use the default scheduler instead of the parent task or the scheduler of the first task

Except for task The default thread pool scheduler used in run () mode, task Start() and taskfactory Startnew() uses the Current TaskScheduler. Therefore, creating a subtask within a task will inherit the task scheduler of the previous task.

Task taskparent = new(() => {
//First nesting
    Task Employer1 = new(() => {
        Console.WriteLine($"Scheduler using parent task ");
        Console.WriteLine($"Employer1 Current TaskScheduler:{TaskScheduler.Current}");
  

    });
  // The of the parent class is rejected Scheduler Scheduler, using the default thread pool task scheduler ThreadPoolTaskScheduler
    Task Employer3 = Employer1.ContinueWith(task => {
    Console.WriteLine($"Refuse to use the scheduler of the parent task and use the default thread pool task scheduler");
    Console.WriteLine($"Employer3 Current TaskScheduler:{TaskScheduler.Current}");

        //Second nesting

        Task Employer2 = new(() => {
               Console.WriteLine($"Scheduler using parent task ");
               Console.WriteLine($"Employer2 Current TaskScheduler:{TaskScheduler.Current}");
              }); Employer2.Start();

    }, TaskContinuationOptions.HideScheduler);
    Employer1.Start();



});

taskparent.Start(new PerThreadTaskScheduler());

Console.WriteLine(taskparent.Status);
Console.Read();
/*output
 *  
WaitingToRun
 Scheduler using parent task
Employer1 Current TaskScheduler:PerThreadTaskScheduler
 Refuse to use the scheduler of the parent task and use the default thread pool task scheduler
Employer3 Current TaskScheduler:System.Threading.Tasks.ThreadPoolTaskScheduler
 Scheduler using parent task
Employer2 Current TaskScheduler:System.Threading.Tasks.ThreadPoolTaskScheduler
 */

 

PerThreadTaskScheduler class
public class PerThreadTaskScheduler : TaskScheduler
{
    protected override IEnumerable<Task> GetScheduledTasks()
    {
        return null;
    }

    protected override void QueueTask(Task task)
    {
        var thread = new Thread(() =>
        {
            TryExecuteTask(task);
        });

        thread.Start();
    }

    protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
    {
        throw new NotImplementedException();
    }
}

 

 

 TaskContinuationOptions.ExecuteSynchronously use case

ContinueWith is the same thread as the first task thread.

using System.Reflection;

Task taskParent = new(() =>
{
 
    Console.WriteLine($"taskParent CurrentId   is  {Task.CurrentId} And Thread{Environment.CurrentManagedThreadId}");
});
 
Task tasktest = taskParent.ContinueWith(tas =>
    {
      Console.WriteLine($"taskParent Status is : {Enum.GetName(taskParent.Status)} and TaskScheduler is {TaskScheduler.Current} ");
      Console.WriteLine($"Continue task CurrentId   is  {Task.CurrentId} And Thread{Environment.CurrentManagedThreadId}");
    }, 
    TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion
    );


taskParent.Start();
Console.Read();

/* Output:
taskParent CurrentId   is  4 And Thread6
taskParent Status is : RanToCompletion and TaskScheduler is System.Threading.Tasks.ThreadPoolTaskScheduler
Continue task CurrentId   is  3 And Thread6

 */

 

 

Other enumeration usage:

//These flags indicate when to run the continue with task
NotOnRanToCompletion =0x10000,
NotOnFaulted=Ox20000,
NotOnCanceled=0x40000,

//These signs are a convenient combination of the above three signs
OnlyonCanceled=NotOnRanToCompletion l NotOnFaulted,
OnlyonFaulted= NotOnRanToCompletion I NotOnCanceled,
OnlyonRanToCompletion = NotOnFaulted | NotonCanceled,
The usage of these enumerations is the same. This case uses onlyoncancelled and onlyonrantocompletement

CancellationTokenSource cts = new CancellationTokenSource();
cts.Token.Register(() =>Console.WriteLine($"Temporary leave"));

Task taskparent = new(() => {
    Task Employer1 = new(() => {

        while (!cts.Token.IsCancellationRequested)
        {
            Thread.Sleep(2000);
            try
            {
                cts.Token.ThrowIfCancellationRequested();
            }
            finally { }
        }

        //Pass in cancellation token, execute continue The token should be called when the task starts

    }, cts.Token);

    Task Employer2 = Employer1.ContinueWith(task => {
        //nameof(Employer1) Prevent forgetting to modify the characters when modifying the variable name. The total characters are channeled
        Console.WriteLine($"{nameof(Employer1)} I asked for leave,{nameof(Employer2)}replace{nameof(Employer1)}work");
        Console.WriteLine("Employer2 start-up   ");

        Thread.Sleep(3000);
        Console.WriteLine($"{nameof(Employer2)}Finished the rest of the work ");
        // Only when Employer1 When the mission is cancelled, Employer2 The task started running
    }, TaskContinuationOptions.OnlyOnCanceled);
    
    Task Employer3 = Employer1.ContinueWith(task => {

        Thread.Sleep(2000);
        Console.WriteLine("Employer3  I wake up ");
        // Only when Employer1 The task does not start until it is completed
    }, TaskContinuationOptions.AttachedToParent|TaskContinuationOptions.OnlyOnRanToCompletion);
    Employer1.Start();

    cts.Cancel();

}  );
 
taskparent.Start();
Console.WriteLine(taskparent.Status);
Console.Read();
/*output
 * Temporary leave
WaitingToRun
 Temporary leave
Employer1 After asking for leave, Employer2 works instead of Employer1
Employer2 start-up
Employer2 Finished the rest of the work*/

TaskContinuationOptions.LongRunning use case

Enable a background thread that is not a thread pool thread.

TaskFactory LongTask = new TaskFactory( TaskCreationOptions.LongRunning,TaskContinuationOptions.AttachedToParent);
TaskFactory preferTask = new TaskFactory( TaskCreationOptions.PreferFairness, TaskContinuationOptions.AttachedToParent);
LongTask.StartNew(() => { 
    Console.WriteLine("Thread.CurrentThread.IsThreadPoolThread: " + Thread.CurrentThread.IsThreadPoolThread);
    Console.WriteLine("Thread.CurrentThread.IsBackground: " + Thread.CurrentThread.IsBackground);

});

Console.ReadKey();
/*
Thread.CurrentThread.IsThreadPoolThread: False
Thread.CurrentThread.IsBackground: True
*/

 

TaskContinuationOptions.PreferFairness

One of the ways to achieve good performance of task parallel library is through "work stealing". . NET 4 thread pool Work stealing is supported for access through the task parallel library and its default scheduler. This shows that each thread in the thread pool has its own work queue; When the thread creates tasks, by default, these tasks will be queued to the thread's local queue instead of to the ThreadPool The call to queueuserworkitem is usually directed to the global queue. When a thread searches for work to be performed, it starts from its local queue. This operation achieves some additional efficiency by improving cache locality and minimizing contention. However, this logic will also affect fairness.

A typical thread pool will have a single queue to maintain all the work to be performed. When the threads in the pool are ready to process another work item, they will unqueue the work from the head of the queue. When the new work arrives in the pool for execution, it will queue to the end of the queue. This provides a degree of fairness between work items because the work item that arrives first is more likely to be selected and executed first.

Stealing work disturbs this fairness. Threads outside the pool may be queuing for work, but if threads in the pool are also generating work, the work generated by the pool will take precedence over other work items, depending on the threads in the pool (these threads first start searching for work using their local queue, only continue into the global queue, and then continue to the queue of other threads (if no local work is available). This behavior is usually expected, or even expected, because if the work item being executed is generating more work, the generated work is usually regarded as part of the overall operation being processed, so it makes sense that it is preferable to other unrelated work. For example, imagine a quick sort operation in which each recursive sort call may lead to several further recursive calls; These calls (which may be a single task in a parallel implementation) are part of a full series of sort operations.

However, in some cases, this default behavior is inappropriate, where fairness should be maintained between specific work items generated by threads in the pool and work items generated by other threads. This is usually the case for the continuation of the long chain, in which the generated work is not regarded as a part of the current work, but the follow-up work of the current work. In these cases, you may want to put the follow-up work together with other work in the system in a fair way. This is taskcreationoptions Where preferfairness can prove useful.

When scheduling a Task to the default scheduler, the scheduler will check whether the current thread from which the Task is queued is a ThreadPool thread with its own local queue. If not, the work item is queued to the global queue. If so, the scheduler will also check whether the TaskCreationOptions value of the Task contains the "preferred fairness" flag, which is not turned on by default. If this flag is set, even if the thread does have its own local queue, the scheduler will queue the Task to the global queue instead of the local queue. In this way, the Task will be considered fairly along with all other work items queued globally.

Just described is the current implementation of the priority fairness flag in the default planner. The implementation can of course be changed, but what will not be changed is the purpose of the flag: by specifying PreferFairness, you can tell the system that this task should not be prioritized just because it comes from a local queue. You are telling the system that you want the system to do its best to ensure that this task is prioritized on a first come, first served basis.

Another thing to note is that the Task itself knows nothing about this flag. It is just a flag, set as an option on the Task. The scheduler determines how it wants to handle this particular option, like taskcreation options Like longrunning. The default scheduler handles it as described above, but another scheduler (such as the one you write) can use this flag as needed, including ignoring it. Therefore, name "preferred" instead of something more stringent like "guarantee".

 

Keywords: C#

Added by chitta_pk on Sun, 06 Feb 2022 07:38:57 +0200