Objective 1.1:  Implement multi-threading and asynchronous processing.

Multiple threads is called parallelism.  Become familiar with System.Threading.Thread.  Allows you to create new threads, manage their priority, and obtain status.

Thread should really only be used when you have special needs.  Other worker objects are better suited for most common multi-threaded situations.

Console is synchronized so multiple threads writing to it is not a hazard.

We can see the new Thread started in the Main() function; the parameter passed is a delegate to the function to use when the thread is started.  The parameter to the ThreadStart parameter is the function to be started when the Thread initializes.  See https://msdn.microsoft.com/en-us/library/system.threading.threadstart(v=vs.110).aspx for more information on the ThreadStart delegate.

Also demonstrated is the Thread.Join() command.  This causes the thread on which the command is executed to pause until all child threads have finished execution.

Another possibility is to run a thread as a background thread.  Foreground threads will keep an application alive; as long as a foreground thread is operating the CLR will not terminate the hosting process.  This is not true for background threads.

Here we see IsBackground being set to true before the Thread is spun up.  Run the application once with it set to true (immediate closure) and false (all of messages are printed out).

How do we pass parameters to the Thread being spun up?  Use a ParameterizedThreadStart method with the Thread.  Pass the objects when you call the Start method and receive them in the ThreadMethod.  They’ll be objects so they’ll have to be cast.

How do you stop a thread?  You can use the Thread.Abort() method, but this is rude and difficult to handle in any semi complex situation.  It’s much better to use a shared variable that both Threads have access to.

Here we can see the Thread continues until we hit a key, which sets the stopThreads variable to true, which in turn causes the second Thread to abort processing.

What about data that we want to store specific to a Thread?  You can associate a state object of some sort with the Thread, or you can use the ThreadStaticAttribute.

When you run this you’ll see ThreadStaticField maintains it’s value separately for each Thread.

Or you can use the ThreadLocal<T> class.  This class takes a delegate to a method that initializes the value to be held (for appropriate calculations in true multi-threaded situations).

Creating a Thread costs resources, as does any object.  You can avoid the cost of instantiating a Thread by using the Thread Pool.  A Thread Pool contains already allocated Threads, ready for use, similar to database connection pooling.  When a thread dies, it is sent back into the Thread Pool.

To work with the ThreadPool you use the QueueUserWorkItem() function off the ThreadPool class.  The Thread is released back to the pool once it has finished executing; no action is needed on your part.

A thread cannot tell you (other than through a shared variable) whether or not the activity it was processing was successful, or whether an error occurred during processing.  For that we step up one level on the evolutionary ladder to the Task class.

Task can tell you if the work allocated was done and if there was a return result, what that result was.

Task.Run returns an allocated task and accepts one parameter – the function to execute with the task.  t.Wait() simply waits for the task to finish processing, similar to Thread.Join().

..to be continued..