C hapter 21 Tasks, Threads and Synchronization in Advanced Programming

(1) Overview

All operations that need to be waited for, for example, because access to files, databases or networks takes a certain amount of time, a new thread can be started and other tasks can be accomplished at the same time.

Threads are independent instruction streams in programs.

 

(2) Paraller class

Paraller class is a good abstraction of threads, which is located in the System.Threading.Tasks namespace and provides data and task parallelism.

The Paraller.For() and Paraller.ForEach() methods call the same code in each iteration, and the two Parallel.Invoke() methods allow different methods to be invoked simultaneously. Paraller.Invoke is used for task parallelism, while Parallel.ForEach is used for data parallelism.

 

1. Parallel.For() method loop

ParallelLoopResult result = Parallel.For(0, 10, i =>
{
    Console.WriteLine("Current iteration order:" + i);
    Thread.Sleep(10);//Thread Waiting
});

In For() method, the first two parameters define the beginning and end of the loop. The third parameter is an Action < int > delegate, and the parameter is the number of iterations of the loop.

The Parallel class waits only for the tasks it creates, not for other background activities.

The Parallel.For() method can stop in advance:

var result = Parallel.For(10, 40, async (int i, ParallelLoopState pls) =>
 {
     Console.WriteLine("Iterative serial number:{0}, task: {1}, thread: {2}", i, Task.CurrentId, Thread.CurrentThread.ManagedThreadId);
     await Task.Delay(10);
     if (i > 15)
     {
         pls.Break();
     }
 });
Console.WriteLine("Cycle completion status:" + result.IsCompleted);
Console.WriteLine("Break Indexes:" + result.LowestBreakIteration);

It should be noted that the Break() method simply tells the loop to exit the iteration outside the current iteration at the appropriate time.

Parallel.For() can also formulate methods for initializing and exiting threads:

Parallel.For<string>(10,25,()=> {
    Console.WriteLine("Initial thread{0},task{1}",Thread.CurrentThread.ManagedThreadId,Task.CurrentId);
    return string.Format("thread Id"+ Thread.CurrentThread.ManagedThreadId);
},
(i,pls,str1)=> {
    Console.WriteLine("Iterative order:[{0}],Thread Initialization Return Value:[{1}],thread Id: [{2}],task Id: [{3}]",i,str1, Thread.CurrentThread.ManagedThreadId, Task.CurrentId);
    Thread.Sleep(10);
    return string.Format("Iterative order:"+i);
},
(str1)=> {
    Console.WriteLine("Thread body return value:{0}",str1);
});

In addition to the designation of the beginning and end of the loop, the third one deals with each thread invoked by the iteration, the fourth one is the method body of the iteration, and the fourth one is the processing of threads at the completion of the iteration.

 

2. Use Paralle.ForEach() method to loop

string[] data = { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k" };
Parallel.ForEach(data, (s, pls, l) =>
{
    Console.WriteLine(s + " " + l);//s Is the value of the item in the current loop, pls yes ParallelLoopState Types, l Is the order of current iterations
});

 

3. Call multiple methods through the Parallel.Invoke() method

The Parallel.Invoke() method runs by passing an array of Action delegates in which methods that need to run in parallel can be specified.

 1 static void Main(string[] args)
 2 {
 3     Parallel.Invoke(Say1, Say2, Say3, Say4, Say5);
 4     Console.WriteLine("---------");
 5     Say1();
 6     Say2();
 7     Say3();
 8     Say4();
 9     Say5();
10  
11     Console.ReadKey();
12 }
13 static void Say1()
14 {
15     Thread.Sleep(100);
16     Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ffff") + "1");
17 }
18 static void Say2()
19 {
20     Thread.Sleep(100);
21     Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ffff") + "2");
22 }
23 static void Say3()
24 {
25     Thread.Sleep(100);
26     Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ffff") + "3");
27 }
28 static void Say4()
29 {
30     Thread.Sleep(100);
31     Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ffff") + "4");
32 }
33 static void Say5()
34 {
35     Thread.Sleep(100);
36     Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ffff") + "5");
37 }

 

 

(3) Tasks

To better control parallel actions, you can use the Task class in the System.Threading.Tasks namespace.

 

1. Start the Task

(1) Tasks using thread pools

 1 private static readonly object locker = new object();
 2 static void Main(string[] args)
 3 {
 4     var tf = new TaskFactory();
 5     Task t1 = tf.StartNew(TaskMethod, "Use TaskFactory");
 6  
 7     Task t2 = Task.Factory.StartNew(TaskMethod, "Use Task.Factory");
 8  
 9     var t3 = new Task(TaskMethod, "Use Task Constructor and start");
10     t3.Start();
11  
12     Task t4 = Task.Run(() => { TaskMethod("Function"); });
13  
14     Console.ReadKey();
15 }
16 static void TaskMethod(object title)
17 {
18     lock (locker)
19     {
20         Console.WriteLine(title);
21         Console.WriteLine("task Id: {0},thread Id: {1}", Task.CurrentId == null ? "no Task" : Task.CurrentId.ToString(), Thread.CurrentThread.ManagedThreadId);
22         Console.WriteLine("Is it a thread pool thread?{0}", Thread.CurrentThread.IsThreadPoolThread);
23         Console.WriteLine("Is it a background thread?{0}",Thread.CurrentThread.IsBackground);
24         Console.WriteLine();
25     }
26 }

 

(2) Synchronization tasks

Tasks do not necessarily use threads in the thread pool, but can also use other threads.

TaskMethod("Main thread call");
var t1 = new Task(TaskMethod,"Synchronized operation");
t1.RunSynchronously();

 

(3) Tasks with separate threads

If the task code should run for a long time, you should use TaskCreationOptions.LongRunning to tell the task scheduler to create a new thread instead of using threads in the thread pool.

var t1 = new Task(TaskMethod, "Long-running tasks", TaskCreationOptions.LongRunning);
t1.Start();

 

2. Future - the result of the task

At the end of the task, it can write some useful state information to the shared object, which must be thread-safe. Another option is to use a task that returns a result, such as Future, which is a generic version of the Task class. When using this class, you can define the type of result that the task returns.

var t1 = new Task<Tuple<int, int>>(TaskWithResult, Tuple.Create<int, int>(10, 5));
t1.Start();
Console.WriteLine(t1.Result);
t1.Wait();
Console.WriteLine("Task results:{0} {1}",t1.Result.Item1, t1.Result.Item2);

 

3. Continuous tasks

Through tasks, you can specify that when a task is completed, another specific task should be started.

Task t1 = new Task(DoOnFirst);
t1.Start();
Task t2 = t1.ContinueWith(DoOnSecond);
Task t3 = t1.ContinueWith(DoOnSecond);
Task t4 = t2.ContinueWith(DoOnSecond);
Task t5 = t3.ContinueWith(DoOnSecond, TaskContinuationOptions.OnlyOnFaulted);//The second parameter refers to t3 Running in case of failure t5

 

4. Task Hierarchy

Tasks can also form a hierarchy. When a task starts a new task, it starts a parent/child hierarchy.

 1 static void Main(string[] args)
 2 {
 3     var parent = new Task(ParentTask);
 4     parent.Start();
 5     Thread.Sleep(2000);
 6     Console.WriteLine(parent.Status);
 7     Thread.Sleep(4000);
 8     Console.WriteLine(parent.Status);
 9     Console.ReadKey();
10 }
11 static void ParentTask()
12 {
13     Console.WriteLine("task Id: "+Task.CurrentId);
14     var child = new Task(ChildTask);
15     child.Start();
16     Thread.Sleep(1000);
17     Console.WriteLine("The parent child task has started running");
18 }
19 static void ChildTask()
20 {
21     Console.WriteLine("Subtask Start");
22     Thread.Sleep(5000);
23     Console.WriteLine("End of Subtask");
24 }

 

 

(4) Cancellation of the structure

NET 4.5 includes a cancellation architecture that allows long-running tasks to be cancelled in a standard way. Cancellation architecture is based on collaborative behavior, which is not mandatory. A long-running task checks whether it has been cancelled and returns control. A Cancellation Token reference is accepted for the method supporting cancellation.

 

1. Cancellation of Parallel.For() method

 1 var cts = new CancellationTokenSource();
 2 cts.Token.Register(() => Console.WriteLine("*** token canceled"));
 3 
 4  
 5 //Send cancellation instructions after 500 milliseconds
 6 cts.CancelAfter(500);
 7 try
 8 {
 9     var result = Parallel.For(0, 100, new ParallelOptions() { CancellationToken = cts.Token, }, x =>
10     {
11         Console.WriteLine("{0}Subcycle begins", x)
12         int sum = 0;
13         for (int i = 0; i < 100; i++)
14         {
15             Thread.Sleep(2);
16             sum += i;
17         }
18         Console.WriteLine("{0}End of Subcycle", x);
19     });
20 }
21 catch (OperationCanceledException ex)
22 {
23     Console.WriteLine(ex.Message);
24 }

Use a new method in. NET 4.5, CancelAfter, to unlabel after 500 milliseconds. Inside the implementation code for the For() loop, the Parallel class validates the results of CanceledToken and cancels the operation. Once the operation is cancelled, the For() method throws an exception of type OperationCanceledException.

 

2. Mission cancellation

The same cancellation mode can also be used for tasks.

 1 var cts = new CancellationTokenSource();
 2 cts.Token.Register(() => Console.WriteLine("*** token canceled"));
 3 
 4 //Send cancellation instructions after 500 milliseconds
 5 cts.CancelAfter(500);
 6  
 7 Task t1 = Task.Run(()=> {
 8     Console.WriteLine("Mission in progress...");
 9     for (int i = 0; i < 20; i++)
10     {
11         Thread.Sleep(100);
12         CancellationToken token = cts.Token;
13         if (token.IsCancellationRequested)
14         {
15             Console.WriteLine("Cancellation request has been sent. Cancellation request comes from the current task");
16             token.ThrowIfCancellationRequested();
17             break;
18         }
19         Console.WriteLine("In Cycle...");
20     }
21     Console.WriteLine("End of Task Not Cancelled");
22 });
23 try
24 {
25     t1.Wait();
26 }
27 catch (AggregateException ex)
28 {
29     Console.WriteLine("Abnormal:{0}, {1}",ex.GetType().Name,ex.Message);
30     foreach (var innerException in ex.InnerExceptions)
31     {
32         Console.WriteLine("Abnormal:{0}, {1}", ex.InnerException.GetType().Name, ex.InnerException.Message);
33     }
34 }

 

 

(5) Thread pool

int nWorkerThreads;
int nCompletionPortThreads;
ThreadPool.GetMaxThreads(out nWorkerThreads, out nCompletionPortThreads);
Console.WriteLine("Maximum number of auxiliary threads in the thread pool:{0}, Asynchronism in Thread Pool I/O Maximum number of threads:{1}",nWorkerThreads,nCompletionPortThreads);
for (int i = 0; i < 5; i++)
{
    ThreadPool.QueueUserWorkItem(JobForAThread);
}
Thread.Sleep(3000);

It should be noted that all threads in the thread pool are background threads. Thread priority and name can not be set. They end with the end of the front threads and are only suitable for short-term tasks.

 

(6) Thread class

This class allows you to create foreground threads and set the priority of threads.

 

1. Passing data to threads

static void Main(string[] args)
{
    var t2 = new Thread(ThreadMainWithParameter);
    t2.Start("Parametric string");
 
    Console.ReadKey();
}
static void ThreadMainWithParameter(object message)
{
    Console.WriteLine("Run the main thread and accept parameters:" + message.ToString());
}

If the ParameterizedThreadStart delegate is used, the entry point of the thread must have an object type parameter and the return type is void.

 

2. Background threads

As long as a foreground thread is running, the process of the application is running. If multiple foreground threads are running and the Main() method is over, the process of the application is still active until all foreground threads complete their tasks.

By default, threads created with the Thread class are foreground threads and threads in the thread pool are background threads. When the Thread class creates threads, you can set the IsBackground property to determine whether to create foreground or background threads.

static void Main(string[] args)
{
    var t1 = new Thread(ThreadMain) { Name = "MyNewThread", IsBackground = false };
    t1.Start();
    Console.WriteLine("The main thread now ends");
    Console.ReadKey();
}

 
private static void ThreadMain()
{
    Console.WriteLine(Thread.CurrentThread.Name+"Threads start running");
    Thread.Sleep(3000);
    Console.WriteLine(Thread.CurrentThread.Name+"Thread termination");
}

When creating threads through the Thread class, set the IsBackground property to false, which is to create a foreground thread. In this case, t1 will not end at the end of the main thread. But if IsBackground is set to true, it will end with the end of the main thread.

 

3. Thread Priority

By assigning priority to threads, the scheduling order of threads can be affected. In the Thread class, you can set the Priority property to affect the basic priority of threads.

 

4. Control threads

Read the ThreadState property of Tread to get the state of the thread.

Thread's Start() method creates a thread whose state is UnStarted.

After the thread scheduler chooses to run, the thread turntable changes to Running.

The Thread.Sleep() method keeps the thread in WaitSleep Join;

The Abort() method of the Thread class triggers an exception of the ThreadAbortException type, and the thread state becomes AbortedRequested, or Aborted if no reset terminates.

The Thread.ResetAbort() method allows threads to continue running after triggering the ThreadAbortException exception.

Join() of the Thread class stops the current thread and waits until the joined thread completes, when the thread state is WaitSleepJoin.

 

 

(7) Thread problem

1. Competition Conditions

If two or more threads access the same object and do not synchronize access to the shared state, contention conditions occur. To avoid this problem, you can lock shared objects. This locks the state variables that are shared in the thread.

private static readonly object locker = new object();

public void ChnageI(int i)
{
    lock (locker)
    {
        if (i == 0)
        {
            i++;
            Console.WriteLine(i == 1);
        }
        i = 0;
    }
}

 

2. Deadlock

Since both threads are waiting for each other, deadlocks occur and threads will wait wirelessly. To avoid this problem, you can design the sequence of locks from the beginning in the application architecture, or you can define a timeout for locks.

 

 

(8) Synchronization

Shared data must be synchronized to ensure that only one thread accesses and changes the shared state at a time. You can use lock statements, Interlock classes, and Monitor classes to synchronize within a process. The Mutex class, Event class, SemaphoreSlim class and ReaderWriterLockSlim class provide thread synchronization between multiple processes.

 

1. lock statement and thread security

lock statements are a simple way to set and unlock.

In the absence of lock statements, multiple threads operate to share data, and none of the results will be correct.

 1 class Program
 2 {
 3     static void Main(string[] args)
 4     {
 5         for (int j = 0; j < 5; j++)
 6         {
 7             int numTasks = 20;
 8             var state = new SharedState();
 9             var tasks = new Task[numTasks];
10             for (int i = 0; i < numTasks; i++)
11             {
12                 tasks[i] = Task.Run(() => { new Job(state).DoTheJob(); });
13             }
14  
15             for (int i = 0; i < numTasks; i++)
16             {
17                 tasks[i].Wait();
18             }
19             Console.WriteLine("The final results are as follows:{0}", state.State);
20         }
21         Console.ReadKey();
22     }
23 }
24 
25 public class SharedState
26 {
27     public int State { get; set; }
28 }
29 
30 public class Job
31 {
32     SharedState sharedState;
33     public Job(SharedState sharedState)
34     {
35         this.sharedState = sharedState;
36     }
37     public void DoTheJob()
38     {
39         for (int i = 0; i < 50000; i++)
40         {
41             sharedState.State += 1;
42         }
43     }
44 }

Modify the DoTheJob() method with the lock statement to get the correct result now.

private readonly object syncRoot = new object();

public void DoTheJob()
{
    for (int i = 0; i < 50000; i++)
    {
        lock (syncRoot)
        {
            sharedState.State += 1;
        }
    }
}

 

2. Interlocked class

The Interlock class is used to atomize simple statements of variables.

public int State
{
    get
    {
        lock (this)
        {
            return ++state;
        }
    }
}

public int State
{
    get
    {
        return Interlocked.Increment(ref state);
    }
}

Using the Interlock class can be faster.

 

3. Monitor class

The lock statement is parsed by the C compiler to use the Monitor class.

lock (syncRoot)
{
    //Code
}
//C#The compiler parses the lock statement to
Monitor.Enter(syncRoot);
try
{
    //Code
}
finally
{
    Monitor.Exit(syncRoot);
}

The Monitor class has the advantage over lock statements: you can add a timeout value waiting to be locked by calling the TryEnter() method.

bool lockTaken = false;
Monitor.TryEnter(syncRoot, 1000, ref lockTaken);
if (lockTaken)
{
    //Acquisition of post-lock operation
    try
    {
        //Code
    }
    finally
    {
        Monitor.Exit(syncRoot);
    }
}
else
{
    //No lock acquisition operation
}

 

4. SpinLock structure

Compared with the high system overhead caused by Monitor garbage collection, using SpinLock structure can effectively reduce the system overhead. SpinLock is very similar to Monitor, but because SpinLock is a structure, assigning a variable to another variable creates a copy.

 

5. WaitHandle base class

WaitHandle is an abstract base class that waits for the setting of a signal. You can wait for different signals because WaitHandle is a base class from which some classes can be derived.

 

6. Mutex class

Mutex(mutual exclusion) is a class in the. NET Framework that provides synchronous access across multiple threads.

In the constructor of the Matux class, you can specify whether the Mutex is initially owned by the tuning thread, define the name of the Mutex, and obtain information about whether the Mutex exists.

bool createdNew;
var mutex = new Mutex(false, "MyMutex", out createdNew);

The system can identify named mutexes and use them to prevent applications from starting twice.

bool createdNew;
var mutex = new Mutex(false, "MyMutex", out createdNew);
if (!createdNew)
{
    Console.WriteLine("Only one application can be started at a time");
    Environment.Exit(0);
}
Console.WriteLine("In operation...");

 

7. Semaphore class

A semaphore is a counting mutex. Semaphores are useful if you need to limit the number of threads that can access available resources.

NET 4.5 provides two classes of Semaphore and SemphoreSlim for semaphore functionality. Semaphore classes can be named, use system-wide resources, and allow synchronization between different processes. SemaphoreSlim class is a lightweight version optimized for shorter waiting times.

 1 static void Main(string[] args)
 2 {
 3     int taskCount = 6;
 4     int semaphoreCount = 3;
 5     var semaphore = new SemaphoreSlim(semaphoreCount, semaphoreCount);
 6     var tasks = new Task[taskCount];
 7 
 8  
 9     for (int i = 0; i < taskCount; i++)
10     {
11         tasks[i] = Task.Run(() =>
12         {
13             TaskMain(semaphore);
14         });
15     }
16 
17     Task.WaitAll(tasks);
18  
19     Console.WriteLine("All tasks have been completed");
20     Console.ReadKey();
21 }
22 
23  
24 private static void TaskMain(SemaphoreSlim semaphore)
25 {
26     bool isCompleted = false;
27     while (!isCompleted)
28     {
29         if (semaphore.Wait(600))
30         {
31             try
32             {
33                 Console.WriteLine("task{0}Locked the signal.", Task.CurrentId);
34                 Thread.Sleep(2000);
35             }
36             finally
37             {
38                 Console.WriteLine("task{0}The signal was released.", Task.CurrentId);
39                 semaphore.Release();
40                 isCompleted = true;
41             }
42         }
43         else
44         {
45             Console.WriteLine("task{0}Timeout, waiting to be executed again", Task.CurrentId);
46         }
47     }
48 }

 

8. Events class

Like mutually exclusive and semaphore objects, events are a system-wide resource synchronization method. To use system events from managed code, the. NET Framework provides Manual ResetEvent, AutoResetEvent, Manual ResetEventSlim and CountdownEvent classes in the System.Threading namespace.

The event keyword in C# has nothing to do with the event class here.

 

9. Barrier class

For synchronization, the Barrier class is well suited for situations where work has multiple task branches and needs to be merged later.

 

10. ReaderWriterLockSlim class

To enable the locking mechanism to allow locking multiple readers to access a resource, the ReaderWriterLockSlim class can be used.

 

(9) Timer class

The. NET Framework provides several Timer classes for calling a method after a certain time interval. System.Threading.Timer, System.Timers.Timer, System.WIndows.Forms.Timer, System.Web.UI.Timer and System.Windows.Threading.Timer.

 

(x) Data stream

Parallel classes, Task classes and Parallel LINQ provide a lot of help for data parallelism. However, these classes can not directly support the processing of data streams and the parallel transformation of data. In this case, the relevant classes in the System.Threading.Tasks.Dataflow namespace are used to handle the problem.

Keywords: C# Windows

Added by prasadharischandra on Fri, 05 Jul 2019 02:23:55 +0300