{%Pragmatic Coding%} Tips and tricks for software developers.


Threading in .NET guide – Part 1

Hello my dear friend!

I'm starting the collection of articles about threading|multithreading programming in C# .NET.

Why now? I'm really wanted to write such collection year ago but as usually work is on the first place. I've got new experience in programming and ready to give materials for your brain in easiest way. So, please, be patient and I'll give samples to be understandable for beginners and for advanced.

Let's get the party!

Part 1 devoted to the introduction in threading.

I have read a lot of material dedicated to threading in .NET and finally want to share my knowledge and with the same way to learn something new. Next part I plan to give you after new year and can't say for sure it's going to be about. Probably it's going to be about Lifecycle of Threads or Synchronization. Just follow me on Tweeter and you'll be in touch with all updates!

Understanding Threading

  • Threads are basically light weight processes responsible for multitasking within a single application.
  • The base class used for threading is System.Threading.
  • Threads are managed under the Common Language Runtime, programmers don't have to manage any threads explicitly.
  • Threads are implemented when you have situations in which you want to perform more then one task at a time.
  • In case of synchronization, since you have limited amount of recourses, you may have to restrict the access to the resources to one thread at a time. In these situations, you may implement locking on the threading to over come the scenarios.
  • An apartment is a logical container within a process and is used for objects that share the same thread-access requirement. Objects in the apartment can all receive method calls from any object in any thread in the apartment. And managed objects (object created within CLR) are responsible for thread safety.
  • Threads can be used under situations where we must wait for an event such as user input, a read from file, or receipt of data over the network.

 Understanding Process and Thread

Each process provides the resources needed to execute a program. A process has a virtual address space, executable code, open handles to system objects, a security context, a unique process identifier, environment variables, a priority class, minimum and maximum working set sizes, and at least one thread of execution. Each process is started with a single thread, often called the primary thread, but can create additional threads from any of its threads.

thread is the entity within a process that can be scheduled for execution. All threads of a process share its virtual address space and system resources. In addition, each thread maintains exception handlers, a scheduling priority, thread local storage, a unique thread identifier, and a set of structures the system will use to save the thread context until it is scheduled. The thread context includes the thread's set of machine registers, the kernel stack, a thread environment block, and a user stack in the address space of the thread's process. Threads can also have their own security context, which can be used for impersonating clients.

Concept and basic sample

A C# client program starts in a single thread created automatically by the CLR and operating system, and is made multithreaded by creating additional threads. Here’s a simple example and its output:

Notice: All threaded applications assume the following namespaces are imported:

The main thread creates a new thread simpleThread on which it runs a method that repeatedly prints the character “A”. Simultaneously, the main thread repeatedly prints the character “B”:
Once started, a thread’s IsAlive property returns true, until the point where the thread ends. A thread ends when the delegate passed to the Thread’s constructor finishes executing. Once ended, a thread cannot restart. The CLR assigns each thread its own memory stack so that local variables are kept separate.

Working with Threads

As we saw in basic sample, threads are created using the Thread class’s constructor, passing in a ThreadStart delegate which indicates where execution should begin. Here’s how the ThreadStart delegate is defined:

Calling Start on the thread then sets it running. The thread continues until its method returns, at which point the thread ends. Here’s an example, using the expanded C# syntax for creating a TheadStart delegate:

In this example, thread t executes Go() — at (much) the same time the main thread calls Go(). The result is two near-instant hellos.

A thread can be created more conveniently by specifying just a method group — and allowing C# to infer the ThreadStart delegate:

Another shortcut is to use a lambda expression or anonymous method:

Passing Data to a Thread

The easiest way to pass arguments to a thread’s target method is to execute a lambda expression that calls the method with the desired arguments:

With this approach, you can pass in any number of arguments to the method. You can even wrap the entire implementation in a multi-statement lambda:

You can do the same thing almost as easily in C# 2.0 with anonymous methods:

Another technique is to pass an argument into Thread’s Start method:

This works because Thread’s constructor is overloaded to accept either of two delegates:

The limitation of ParameterizedThreadStart is that it accepts only one argument. And because it’s of type object, it usually needs to be cast.

Lambda expressions and captured variables

As we saw, a lambda expression is the most powerful way to pass data to a thread. However, you must be careful about accidentally modifying captured variables after starting the thread, because these variables are shared. For instance, consider the following:

The output is nondeterministic! Here’s a typical result:

The problem is that the i variable refers to the same memory location throughout the loop’s lifetime. Therefore, each thread calls Console.Write on a variable whose value may change as it is running!

The solution is to use a temporary variable as follows:

Variable temp is now local to each loop iteration. Therefore, each thread captures a different memory location and there’s no problem. We can illustrate the problem in the earlier code more simply with the following example:

Because both lambda expressions capture the same text variable, t2 is printed twice:

Foreground and Background Threads

By default, threads you create explicitly are foreground threads. Foreground threads keep the application alive for as long as any one of them is running, whereas background threads do not. Once all foreground threads finish, the application ends, and any background threads still running abruptly terminate.

A thread’s foreground/background status has no relation to its priority or allocation of execution time.

You can query or change a thread’s background status using its IsBackground property. Here’s an example:

If this program is called with no arguments, the worker thread assumes foreground status and will wait on the ReadLine statement for the user to press Enter. Meanwhile, the main thread exits, but the application keeps running because a foreground thread is still alive.

On the other hand, if an argument is passed to Main(), the worker is assigned background status, and the program exits almost immediately as the main thread ends (terminating the ReadLine).

When a process terminates in this manner, any finally blocks in the execution stack of background threads are circumvented. This is a problem if your program employs finally (or using) blocks to perform cleanup work such as releasing resources or deleting temporary files. To avoid this, you can explicitly wait out such background threads upon exiting an application. There are two ways to accomplish this:

  • If you’ve created the thread yourself, call Join on the thread.
  • If you’re on a pooled thread, use an event wait handle.

In either case, you should specify a timeout, so you can abandon a renegade thread should it refuse to finish for some reason. This is your backup exit strategy: in the end, you want your application to close — without the user having to enlist help from the Task Manager!

If a user uses the Task Manager to forcibly end a .NET process, all threads “drop dead” as though they were background threads. This is observed rather than documented behavior, and it could vary depending on the CLR and operating system version.

Foreground threads don’t require this treatment, but you must take care to avoid bugs that could cause the thread not to end. A common cause for applications failing to exit properly is the presence of active foreground threads.

Thread Priority

A thread’s Priority property determines how much execution time it gets relative to other active threads in the operating system, on the following scale:

This becomes relevant only when multiple threads are simultaneously active.

Think carefully before elevating a thread’s priority — it can lead to problems such as resource starvation for other threads.

Elevating a thread’s priority doesn’t make it capable of performing real-time work, because it’s still throttled by the application’s process priority. To perform real-time work, you must also elevate the process priority using the Processclass in System.Diagnostics (we didn’t tell you how to do this):

ProcessPriorityClass.High is actually one notch short of the highest priority: Realtime. Setting a process priority to Realtime instructs the OS that you never want the process to yield CPU time to another process. If your program enters an accidental infinite loop, you might find even the operating system locked out, with nothing short of the power button left to rescue you! For this reason, High is usually the best choice for real-time applications.

If your real-time application has a user interface, elevating the process priority gives screen updates excessive CPU time, slowing down the entire computer (particularly if the UI is complex). Lowering the main thread’s priority in conjunction with raising the process’s priority ensures that the real-time thread doesn’t get preempted by screen redraws, but doesn’t solve the problem of starving other applications of CPU time, because the operating system will still allocate disproportionate resources to the process as a whole. An ideal solution is to have the real-time worker and user interface run as separate applications with different process priorities, communicating via Remoting or memory-mapped files. Memory-mapped files are ideally suited to this task.

Even with an elevated process priority, there’s a limit to the suitability of the managed environment in handling hard real-time requirements. In addition to the issues of latency introduced by automatic garbage collection, the operating system may present additional challenges — even for unmanaged applications — that are best solved with dedicated hardware or a specialized real-time platform.

Exception Handling

Any try/catch/finally blocks in scope when a thread is created are of no relevance to the thread when it starts executing. Consider the following program:

The try/catch statement in this example is ineffective, and the newly created thread will be encumbered with an unhandled NullReferenceException. This behavior makes sense when you consider that each thread has an independent execution path.

The remedy is to move the exception handler into the Go method:

You need an exception handler on all thread entry methods in production applications — just as you do (usually at a higher level, in the execution stack) on your main thread. An unhandled exception causes the whole application to shut down. With an ugly dialog!

In writing such exception handling blocks, rarely would you ignore the error: typically, you’d log the details of the exception, and then perhaps display a dialog allowing the user to automatically submit those details to your web server. You then might shut down the application — because it’s possible that the error corrupted the program’s state. However, the cost of doing so is that the user will lose his recent work — open documents, for instance.

The “global” exception handling events for WPF and Windows Forms applications (Application.DispatcherUnhandledException and Application.ThreadException) fire only for exceptions thrown on the main UI thread. You still must handle exceptions on worker threads manually.

AppDomain.CurrentDomain.UnhandledException fires on any unhandled exception, but provides no means of preventing the application from shutting down afterward.

There are, however, some cases where you don’t need to handle exceptions on a worker thread, because the .NET Framework does it for you. These are covered in upcoming sections, and are:

  • Asynchronous delegates
  • BackgroundWorker
  • The Task Parallel Library (conditions apply)

PriorityLevels of Thread

Any programmer can decide a priority for their thread, but ultimately, it's up to the recipient to decide what should be acted upon now and what can wait.

Windows uses a priority system from 0-31 where 31 is the highest. Anything higher than 15 needs to be done via an Administrator. Threads that have priority between 16-31 are considered real time, and will pre-empt lower priority level threads. Think about drivers/input devices and things like them; these will be running with priorities between 16-31.

And we have options when we use code, as the System.Threading.Thread class exposes a Priority property. If we look at what MSDN states, we could set on of the following priority levels of thread is set or checked by using an enumeration i.e. ThreadPriority. The valid values are for this enumeration are;

  • Highest
  • AboveNormal
  • Normal
  • BelowNormal
  • Lowest

Note: Operating Systems are not required to honor the priority of a thread. Be careful what you do when setting priorities for threads.

End of part 1

Looks like this the main information I wanted to say about threads. Threading is a complex subject, and as such, this series will be quite hard, but I think worth a read.

Thanks for your patient! Hope this helpful! Feel free to leave a comment!

Share on social network

Share to Facebook
Share to Google Plus
Share to LiveJournal
Share to MyWorld
Share to Odnoklassniki
Share to Yandex
Comments (0) Trackbacks (0)

No comments yet.

Leave a comment

No trackbacks yet.