To get a deeper insight in what asynchronous programming is, refer to my Introduction. For starters asynchronous code simply has more than one piece of logic executed at the same time.

In this post we are going to look at ways to accomplish asynchronous execution with programming techniques in .Net.

What this post will cover

  • Overview of asynchronous API in .Net
  • Overview of .Net asynchronous techniques

What this post will not cover

  •  Mutable Shared state and its complications
  •  Every inch of these API´s (look at the single Posts for this)

In general the .Net asynchronous APIs developed over the time from .Net 1.0 to the current version, where each iteration tried to simplify the API, or improve it in certain regards. The biggest issue,with asynchrounus programming, aside from the mutable shared state issue, is that asynchronous code inherently looks quite different to synchronous code.

The goal for the APIs is to come closer to the synchronous code, and with the async/await keywords and Tasks from TPL it comes pretty close. But we are getting ahead of ourselves. Lets talk about the most basic way of doing async programming first: Threads and the Thread pool.

Threads

The System.Threading.Thread is a one-to-one mapping of a OS thread. Threads in general are used for long running, specialized work. This can amount to monitoring a device or executing code with low priority. The Thread class actually gives you lots of control over the actual thread, this ranges from starting and stopping a thread, to in what context it runs, what kind of thread it its etc.

To start another thread you simply use the Thread class directly

The delegate to the thread returns void and has no parameters. (this is .Net 1.0) You needed to encapsulate the data that should be supplied to the Thread in an extra class. This was not to complicated but resulted in more code to maintain.
From .Net 2.0 the Thread constructor cold take a ParametrizedThreadStart delegate that has an object as parameter.

To stop a thread you could wait for the delegated method to return or call one of the thread methods:

  • thread.Abort();
  • thread.Interrupt();

Both raise an interception to stop the thread, but neither is advisable to use.

Interrupt throws its exception when the thread in question is in the state of WaitSleepJoin. Yet not every application code is equipped to deal with this in the right manner. Abort is even more severe, because it throws an exception that might be detrimental to the executing thread at the time you invoke this method.

There are two solutions to this, the first one is to refrain to cooperation. That is have the thread equip a with boolean flag, that the thread needs to check periodically. The issue again with this approach might be that blocking code can not check the flag. Another more subtle issue is that JIT Compiler can sometimes modifiy code in a way that is safe to do in single threaded but not multi threaded code. This in turn can be alluded by the volatile keyword before the variable.

The second method has much more overhead but is also more of a safe way. You could spawn a new process with the Thread in it (or even an AppDomain in .Net) and just terminate the process or the AppDomain in the situation where you want to stop the thread.

Foreground and Background threads

Threads in a .Net application and any process for that matter, come in two flavors. The foreground and the background thread. As long as at least one foreground thread is alive, the process can be alive. If only background threads are alive and no foreground threads, the process terminates and nongracefully terminates all alive background threads. This can be an issue of itself, because the background threads never get to clean up after themselves.

Background threads are helpful, because one does not need to be responsible for controlling the termination of threads, but the non cleanup issue still holds true.

All Threads created with Thread class are foreground threads. To make a background thread, you need to set IsBackground proeprty to true.

Coordinating threads with Join

If you spawn a thread, the spawning code might want know when that thread finishes in a regular manner.
The thread.Join() method allows to monitor another thread for its completion. If you call this it either blocks indefinetly (until the thread is done) or can be used with timeout with a boolean flag, whereas the boolean is set to true if ran to completion before timeout and false otherwise.

Notice that the thread still runs if it timeouts, onyl the wait has haulted.
Just as a general rule in async programming, and especially with using a thread.Join(), do always prefer waiting with timeout.

With the other async APIs we will look at in this post, you might ask why would you want to use the Thread class directly. But even with .Net 4.7 and TPL there are reasons to use the thread class. One is to interact with COM objects.

The Thread pool

The Thread class might be easy to use, but is a very expensive resource. Remember that each thread has its own stack, which amounts to 1MB of memory, and its own Thread Local Storage slots.

The solution to this is the notion of the thread pool, which saves up used threads to reuse them. This Thread pool is a process wide resource that provides for efficient use of threads. In general the application enqueues a work item to the thread pool and any available threads dequeues the item. (there again is the queueing pattern see Introduction)

If all threads are doing work, the thread pool manager starts adding threads, according to its heuristics. If a thread completes its current work, it returns back to the queue and gets its next item.
Idle threads will eventually be degraded, i.e. terminated, when no work items are getting enqueued. In this way the thread pool manager essentially balances the work to be done, with the amount of threads available.

Remodeling of Thread pool with .Net 4.0

The thread pool was implemented as a linked list until .Net 4.0. Since then it was changed to a linked list of arrays. One of the reasons is the PFx (Parallel Framework Extension) which constituted for a lot of work in the thread pool.
With the former implementation the thread pool became a large data structure over time, which was very expensive for the Garbage Collector during its marking phase. It is also a terrible data structure for concurrent manipulation because of the associated node manipulation. As a result the processing across the cores would have been needed to serialized to update the queue.

With the remodeling it was built with arrays of work items, which then were connected as a linked list.
This resulted in lot fewer references. Adding and moving items is now an array index manipulation, which is way cheaper to make thread safe.

Another result of the remodeling are work stealing queues. The internals of the thread pool were modified to give each thread its own queue instead of a single queue for all threads. If a thread pool thread creates async work, by defaut it will be put on the local queue of that thread. When a thread finishes its current work item it looks for new work items in the following order:

  1. local queue
  2. global queue
  3. other local queues (from other threads)

Worker and I/O threads

There are two kinds of thread in the thread pool: worker and I/O threads. Worker threads are targeted at CPU bound work. Which essentially means long running computations. If you would run I/O operations on those threads it would be a waste of resources, because the thread would be idle waiting until the I/O operation is completed. It is better to use specified I/O threads that listen to I/O completion ports which then in turn notify the I/O threads when the hardware operation has finished.

Getting work onto the Thread Pool

With this groundwork, it is now time to look at the ways work items can be enqueued for the thread pool.

  1.  ThreadPool.QueueUserWorkItem
    This is the most direct way. This method uses a WaitCallBack delegate with object as parameter.
  2. Timers
    Intervals that perofrm a regularly repeated task. The timer stops when it is disposed of.
  3.  APM
    This is the topic for the next page.

 

Leave a Reply