Threading

A thread is a virtual processor that can run some c3ode from any AppDomain. Although at any single time a thread can run code from a single domain, it can cross domain boundaries when needed. Indeed, in the preceding example, there is only a single thread that did the entire job for all three AppDomains.

Without diving into the internals of Windows threading, we should know that a CLR thread is actually a Windows thread. This one has a high creation cost (even worse for 64-bit systems) as in any other virtualization technique, although in Windows, creating a process is even worse in terms of resource usage. This is why Microsoft has supported multi-threading programming in its operating systems since the age of Windows NT.

What is important to know is that a thread never has 100 percent of CPU's time because its CPU time is reassigned to any new pending-for-work threads every 30 milliseconds (a time-slice) by acting what we call in Windows a context switch.

This ensures that at the operating system level, no process can harm the system stability by locking every CPU forever and thus stopping critical OS tasks. This is why Windows is definitely a time-sharing operating system.

In the .NET world, a thread can be created by starting the Run method of the Thread class. Thus, the simple instantiation of the object does nothing more than instantiate any other class. A thread must always have an entry point: a starting method that can have an initialization parameter, usually referred to as state— that is actually anything within the .NET class hierarchy.

A Priority configuration is available and mapped to Windows' thread in order to alter the results in the context-switching search for new threads. Usually, priorities higher than normal are dangerous for system stability (in rare cases, letting the OS reach the starvation state that occurs when the highest-priority thread prevents context switching), while lower priorities are often used to process no CPU-time critical operations.

An IsBackground property is available to any Thread class instance. Setting this property to True will signal to the CLR that this is a non-blocking thread—a background thread—in that its execution does not keep a process in the running state. On the other hand, upon setting it to False (default value), CLR will consider this thread as a foreground thread, in that its execution will keep the whole process in the running state.

Operations such as the animation of a clock may surely be made on a background thread, while the non-blocking UI operation of saving a huge file on a network resource is surely a candidate to run in a foreground thread, because although asynchronous against the UI, the foreground thread is also needed, and an eventual process premature exit should not kill such a thread. It is clear that CLR will automatically kill any background thread when a process ends without giving them any time to preserve any eventually needed data consistency.

Here's an example code on creating a background thread with low-priority CPU time:

static void Main(string[] args)
{
    //thread creation
    var t1 = new Thread(OtherThreadStartHere);

    //set thread priority at starting
    t1.Priority = ThreadPriority.Lowest;

    //set thread as background
    t1.IsBackground = true;

    //thread start will cause CLR asks a Thread to Windows
    t1.Start();

    //lock current executing thread up to the end of the t1 thread
    t1.Join();
}

private static void OtherThreadStartHere()
{
    //eventually change priority from innerside
    Thread.CurrentThread.Priority = ThreadPriority.Normal;

    //do something
}

The Thread class has specific methods to configure (as said) or handle thread lifetime, such as Start, to create a new OS thread and Join to kill a OS thread, and get back the remote thread status on the caller thread, such as any available exception.

Other methods are available, such as Suspend, Resume (both deprecated), and Abort (still not deprecated). It is easy to imagine that by invoking the Suspend or Resume method, the CLR will pause the thread from running or resuming work. Instead, the Abort method will inject a ThreadAbortException event at the current execution point of the thread's inner code, acting as a thread stopper. Although this will actually stop the thread from working, it is easy to infer that it is not an elegant solution because it can easily produce an inconsistent data state.

To solve this issue, CLR gives us the BeginCriticalRegion method to signal the beginning of an unable-to-abort code block and an EndCriticalRegion method to end such a code portion. Such methods will prevent any ThreadAbortException event being raised in such a portion of the atomic code. Here's an example code:

static void Main(string[] args)
{
    //thread creation
    var t1 = new Thread(OtherThreadStartHere);

    //thread start will cause CLR asks a Thread to Windows
    t1.Start();

    //do something

    t1.Abort();
}

private static void OtherThreadStartHere()
{
    for (int i = 0; i < 100; i++)
    {
        Thread.Sleep(100);

        //signal this is an atomic code region
        //an Abort will never break execution of this code portion
        Thread.BeginCriticalRegion();

        //atomic code
        //atomic code
        //atomic code
        //atomic code

        Thread.EndCriticalRegion();
    }
}

Tip

When dealing with iterated functions within a thread, instead of using Abort and Critical sections to gently signal the thread to exit, simply use a field as a flag (something like canContinue) to check within the iterated function, such as while(canContinue). This choice will behave in a similar way to the previous example, without having to raise a useless exception.

Other interesting methods of the Thread class are Sleep (accepts a millisecond parameter) and Yield. The Sleep method suspends the thread for the given time; alternately, when 0 is used as a parameter, it signals a context switch to change the state to suspended, eventually causing higher-priority threads to use the thread time-slice as soon as possible. A better choice—when you want to recycle some of the time-slice time if a thread actually ended its job prematurely—is to use the Yield method that will give the remaining time-slice the next queued thread as soon as possible, waiting for the CPU time of the same processor. Here is an example code:

private static void OtherThreadStartHere()
{
    //change state to suspended and wait 1000 ms
    Thread.Sleep(1000);

    //change state to suspended
    Thread.Sleep(0);

    //give remaining time-slice to the next queued thread of current CPU
    Thread.Yield();
}

If we are in search of an alternative to create a thread from scratch with the Thread class, we could use an already created-thread preserved in CLR for any unimportant jobs that we can usually make in a background thread. These threads are contained in a collection named as ThreadPool. Many other CLR classes use threads from the ThreadPool collection, so if a lot of jobs are going to be queued in it, remember to increase the minimum and maximum pool size:

static void Main(string[] args)
{
    //set minimum thread pool size
    ThreadPool.SetMinThreads(32, 32);

    //set maximum thread pool size
    ThreadPool.SetMaxThreads(512, 512);

    //start a background operation within a thread from threadpool
    //as soon as when a thread will became available
    ThreadPool.QueueUserWorkItem(ExecuteInBackgroundThread);
}

private static void ExecuteInBackgroundThread(object state)
{
    //do something
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.22.181.47