ENTERPRISE

Programming .NET Components : Working with Threads (part 3) - Blocking Threads

12/24/2013 8:34:54 PM

2. Blocking Threads

The Thread class provides a number of methods you can use to block the execution of a thread, similar in their effect to the native mechanisms available to Windows programmers. These include suspending a thread, putting a thread to sleep, waiting for a thread to die. Developers often misuse these mechanisms without ever realizing they were doing anything wrong. This section outlines the various blocking options and discusses why it's a bad idea to use most of them.

2.1. Suspending and resuming a thread

The Thread class provides the Suspend( ) method, which suspends the execution of a thread, and the Resume( ) method, which resumes a suspended thread:

    public sealed class Thread
{
public void Resume( );
public void Suspend( );
//Other methods and properties
}

Anybody can call Suspend( ) on a Thread object, including objects running on that thread, and there is no harm in calling Suspend( ) on an already suspended thread. Obviously, only clients on other threads can resume a suspended thread. Suspend( ) is a non-blocking call, meaning that control returns immediately to the caller and the thread is suspended later, usually at the next safe point. A safe point is a point in the code where it's safe for garbage collection to take place. The JIT compiler identifies those points in the code that are safe for suspending the thread (such as when returning from method calls or branching for another loop iteration). When Suspend( ) is called, the thread is suspended once it reaches the next safe point.

The bottom line is that suspending a thread isn't an instantaneous operation. The need to suspend and then resume a thread usually results from a need to synchronize the execution of that thread with other threads, but using Suspend( ) and Resume( ) for that purpose isn't recommended because there is no telling when these operations will take place. Consequently, .NET 2.0 applies the Obsolete attribute to Suspend( ) and Resume( ), warning you not to use them. If you need to suspend the execution of a thread and then resume it later, you should use the dedicated .NET synchronization objects (described later). The synchronization objects provide a deterministic way of blocking a thread or signaling it to continue executing. In general, you should avoid explicitly suspending and resuming threads.

2.2. Putting a thread to sleep

The Thread class provides two overloaded versions of the static Sleep( ) method, which puts a thread to sleep for a specified timeout:

    public sealed class Thread
{
public static void Sleep(int millisecondsTimeout);
public static void Sleep(TimeSpan timeout);
//Other methods and properties
}

Because Sleep( ) is a static method, you can put only your own thread to sleep:

    Thread.Sleep(20);//Sleep for 20 milliseconds

Sleep( ) is a blocking call, meaning that control returns to the calling thread only after the sleep period has elapsed. Sleep( ) puts the thread in a special queue of threads waiting to be awakened by the operating system. Any thread that calls Sleep( ) willingly relinquishes the remainder of its allocated CPU time slot, even if the sleep timeout is less than the remainder of the time slot. Consequently, calling Sleep( ) with a timeout of zero is a way to force a thread context switch:

    Thread.Sleep(0);//Forces a context switch

If no other thread with the same or higher priority is ready to run, control returns to the thread .

You can also put a thread to sleep indefinitely, using the Infinite static constant of the Timeout class:

    Thread.Sleep(Timeout.Infinite);

Of course, putting a thread to sleep indefinitely is an inefficient use of the system services; it's better to simply terminate the thread (by returning from the thread method). If you need to block a thread until some event takes place, use .NET synchronization objects. In fact, you should generally avoid putting a thread to sleep, unless you specifically want the thread to act as a kind of timer. Traditionally, you put threads to sleep to cope with race conditions, by explicitly removing some of the threads involved in the race condition. A race condition is a situation in which thread T1 needs to have another thread, T2, complete a task or reach a certain state. The race condition occurs when T1 proceeds as if T2 is ready, when in fact it may not be. Sometimes T1 has its own processing to do, and that (in a poorly designed system) usually keeps it busy long enough to avoid the race condition. Occasionally, however, T1 will complete before T2 is ready, and an error will occur. Using Sleep( ) to resolve a race condition is inappropriate, because it doesn't address the root cause of the race condition (usually, the lack of proper synchronization between the participating threads). Putting threads to sleep is at best a makeshift solution, because the race condition can still manifest itself in different ways; also, it isn't likely to work when more threads get involved. Avoid putting a thread to sleep, and use .NET synchronization objects instead.

: A Minute for TimeSpan

Traditionally, most APIs in Windows that deal with time use some form of physical time measurement, such as seconds or milliseconds. You probably have no problem converting a minute or two to seconds. However, it's harder to convert 1 hour and 48 minutes into seconds, or 2 days. The TimeSpan struct addresses this issue by providing many methods for time conversion and representing time periods in a uniform manner. For example, if you need to represent 2 days, use the static method FromDays( ), which returns a TimeSpan value representing 2 days:

    TimeSpan TimeSpan = TimeSpan.FromDays(2);


2.3. Spinning while waiting

The Thread class provides another sleep-like operation, called SpinWait( ):

    public static void SpinWait(int iterations);

When a thread calls SpinWait( ), the calling thread waits the number of iterations specified but is never added to the queue of waiting threads. As a result, the thread is effectively put to sleep without relinquishing the remainder of its CPU time slot. The .NET documentation doesn't define what an iteration is, but it's likely mapped to a predetermined number (probably just one) of no-operation (NOP) assembly instructions. Consequently, the following SpinWait( ) instruction will take a different amount of time to complete on machines with different CPU clock speeds:

    int long Million = 1000000;
Thread.SpinWait(Million);

SpinWait( ) isn't intended to replace Sleep( ), but rather is available as an advanced optimization technique. If you know that some resource your thread is waiting for will become available in the immediate future, it's potentially more efficient to spin and wait than it would be to use either Sleep( ) or a synchronization object, because these force a thread context switch, which is one of the most expensive operations performed by the operating system. However, even in the esoteric cases for which SpinWait( ) was designed, using it amounts to an educated guess at best. SpinWait( ) gains you nothing if the resource isn't available at the end of the call, or if the operating system preempts your thread because its time slot has elapsed or because another thread with a higher priority is ready to run. In general, I recommend that you always use deterministic programming (synchronization objects, in this case) and avoid optimization techniques.

2.4. Joining a thread

The Thread class's Join( ) method allows one thread to wait for another thread to terminate. Any client that has a reference to a Thread object can call Join( ) and have the client thread blocked until the thread terminates:

    static void WaitForThreadToDie(Thread thread)
{
thread.Join( );
}

Join( ) returns regardless of the cause of death—either natural (the thread returns from the thread method) or unnatural (the thread encounters an exception).

Note that it is imperative to always check before calling Join( ) that you are not joining your own thread:

    static void WaitForThreadToDie(Thread thread)
{
Debug.Assert(Thread.CurrentThread.ManagedThreadId !=
thread.ManagedThreadId);
thread.Join( );
}

Doing so will prevent a deadlock of waiting for your own thread to die. Join( ) is useful when dealing with application shutdown; when an application starts its shutdown procedure, it typically signals all the worker threads to terminate and then waits for the threads to terminate before proceeding with the shutdown. The standard way of doing this is to call Join( ) on the worker threads. Calling Join( ) is similar to waiting on a thread handle in the Win32 world, and it's likely that the Join( ) method implementation does just that.

The Join( ) method has two overloaded versions, allowing you to specify a waiting timeout:

    public sealed class Thread
{
public void Join( );
public bool Join(int millisecondsTimeout);
public bool Join(TimeSpan timeout);
//Other methods and properties
}

When you specify a timeout, Join( ) returns when the timeout has expired or when the thread is terminated, whichever happens first. The bool return value is set to false if the timeout has elapsed but the thread is still running, and to true if the thread is dead.

2.5. Interrupting a waiting thread

You can rudely awaken a sleeping or waiting thread by calling the Interrupt( ) method of the Thread class:

    public void Interrupt(  );

Calling Interrupt( ) unblocks a sleeping thread (or a waiting thread, such as a thread that called Join( ) on another thread) and throws an exception of type ThreadInterruptedException in the unblocked thread. If the code the thread executes doesn't catch that exception, the thread is terminated by the runtime.

If a call to Thread.Interrupt( ) is made on a thread that isn't sleeping or waiting, the next time the thread tries to go to sleep or wait .NET immediately throws an exception of type ThreadInterruptedException in its call stack. Note, however, that calling Interrupt( ) doesn't interrupt a thread that is executing unmanaged code via interop; nor does it interrupt a thread that is in the middle of a call to SpinWait( ), because as far as the operating system is concerned that thread is not actually waiting at all.

Again, you should avoid relying on drastic solutions such as throwing exceptions to unblock another thread. Use .NET synchronization objects instead, to gain the benefits of structured and deterministic code flow.

Other  
  •  System Center Configuration Manager 2007 : Integrating Virtual Applications (part 3) - Creating Adobe Reader as a Virtual Application in ConfigMgr R2
  •  System Center Configuration Manager 2007 : Integrating Virtual Applications (part 2) - Activating Application Virtualization in ConfigMgr 2007 R2
  •  System Center Configuration Manager 2007 : Integrating Virtual Applications (part 1) - What Is SoftGrid?
  •  Microsoft Lync Server 2010 : Planning for Internal Non-Voice Deployment - Planning for Archiving (part 2)
  •  Microsoft Lync Server 2010 : Planning for Internal Non-Voice Deployment - Planning for Archiving (part 1)
  •  Microsoft Lync Server 2010 : Planning for Internal Non-Voice Deployment - Planning for Conferencing
  •  Microsoft Lync Server 2010 : Planning for Internal Non-Voice Deployment - Planning for IM
  •  Microsoft Lync Server 2010 : Planning for Internal Non-Voice Deployment - Planning for Capacity
  •  Microsoft Lync Server 2010 : Planning for Internal Non-Voice Deployment - Determining Your Infrastructure Needs
  •  Microsoft Lync Server 2010 : Determining the Scope of the Deployment
  •  
    Top 10
    Review : Sigma 24mm f/1.4 DG HSM Art
    Review : Canon EF11-24mm f/4L USM
    Review : Creative Sound Blaster Roar 2
    Review : Philips Fidelio M2L
    Review : Alienware 17 - Dell's Alienware laptops
    Review Smartwatch : Wellograph
    Review : Xiaomi Redmi 2
    Extending LINQ to Objects : Writing a Single Element Operator (part 2) - Building the RandomElement Operator
    Extending LINQ to Objects : Writing a Single Element Operator (part 1) - Building Our Own Last Operator
    3 Tips for Maintaining Your Cell Phone Battery (part 2) - Discharge Smart, Use Smart
    REVIEW
    - First look: Apple Watch

    - 3 Tips for Maintaining Your Cell Phone Battery (part 1)

    - 3 Tips for Maintaining Your Cell Phone Battery (part 2)
    VIDEO TUTORIAL
    - How to create your first Swimlane Diagram or Cross-Functional Flowchart Diagram by using Microsoft Visio 2010 (Part 1)

    - How to create your first Swimlane Diagram or Cross-Functional Flowchart Diagram by using Microsoft Visio 2010 (Part 2)

    - How to create your first Swimlane Diagram or Cross-Functional Flowchart Diagram by using Microsoft Visio 2010 (Part 3)
    Popular Tags
    Microsoft Access Microsoft Excel Microsoft OneNote Microsoft PowerPoint Microsoft Project Microsoft Visio Microsoft Word Active Directory Biztalk Exchange Server Microsoft LynC Server Microsoft Dynamic Sharepoint Sql Server Windows Server 2008 Windows Server 2012 Windows 7 Windows 8 Adobe Indesign Adobe Flash Professional Dreamweaver Adobe Illustrator Adobe After Effects Adobe Photoshop Adobe Fireworks Adobe Flash Catalyst Corel Painter X CorelDRAW X5 CorelDraw 10 QuarkXPress 8 windows Phone 7 windows Phone 8