Раздел «Технологии программирования».ThreadingModule:

Многопоточное приложение


Summary

Whether you are developing for computers with one processor or several, you want your application to provide the most responsive interaction with the user, even if the application is currently doing other work. Using multiple threads of execution is one of the most powerful ways to keep your application responsive to the user and at the same time make use of the processor in between or even during user events.

Использование многопоточности

Operating systems use processes to separate the different applications that they are executing. Threads are the basic unit to which an operating system allocates processor time, and more than one thread can be executing code inside that process. Each thread maintains exception handlers, a scheduling priority, and a set of structures the system uses to save the thread context until it is scheduled. The thread context includes all the information the thread needs to seamlessly resume execution, including the thread's set of CPU registers and stack, in the address space of the thread's host process.

Преимущества

Without modification, the same application would dramatically increase user satisfaction when run on a computer with more than one processor. Your single application domain could use multiple threads to accomplish the following tasks:

Недостатки

It is recommended that you use as few threads as possible, thereby minimizing the use of operating-system resources and improving performance. Threading also has resource requirements and potential conflicts to be considered when designing your application. The resource requirements are as follows:

Providing shared access to resources can create conflicts. To avoid conflicts, you must synchronize, or control the access to, shared resources.

Resources that require synchronization include:

Использование нитей

In general, using the ThreadPool class is the easiest way to handle multiple threads for relatively short tasks that will not block other threads and when you do not expect any particular scheduling of the tasks. However, there are a number of reasons to create your own threads:

Создание дополнительного потока

Creating a new instance of a Thread object creates new managed threads. As its only parameter, the constructor for Thread takes a ThreadStart delegate that wraps the method that will be invoked by the new Thread when you call Thread.Start. Calling Thread.Start more than once will cause a ThreadStateException to be thrown.

Thread.Start submits an asynchronous request to the system, and the call returns immediately, possibly before the new thread has actually started. You can use Thread.ThreadState and Thread.IsAlive to determine the state of the thread at any one moment. Thread.Abort aborts a thread, marking it for garbage collection. The following code example creates two new threads to call instance and static methods on another object.

using System;
using System.Threading;

public class ServerClass{
   // The method that will be called when the thread is started.
   public void InstanceMethod(){
      Console.WriteLine("ServerClass.InstanceMethod is running on another thread.");
      // Pause for a moment to provide a delay to make threads more apparent.
      Thread.Sleep(3000);
      Console.WriteLine("The instance method called by the worker thread has ended.");
   }

   public static void StaticMethod(){
      Console.WriteLine("ServerClass.StaticMethod is running on another thread.");
      // Pause for a moment to provide a delay to make threads more apparent.
      Thread.Sleep(5000);
      Console.WriteLine("The static method called by the worker thread has ended.");
   }
}

public class Simple{
   public static int Main(String[] args){
      Console.WriteLine("Thread Simple Sample");

      ServerClass serverObject = new ServerClass();

      // Create the thread object, passing in the 
      // serverObject.InstanceMethod method using a ThreadStart delegate.
      Thread InstanceCaller = new Thread(new ThreadStart(serverObject.InstanceMethod));

      // Start the thread.
      InstanceCaller.Start();

      Console.WriteLine("The Main() thread calls this after starting the new InstanceCaller thread.");

      // Create the thread object, passing in the 
      // serverObject.StaticMethod method using a ThreadStart delegate.
      Thread StaticCaller = new Thread(new ThreadStart(ServerClass.StaticMethod));

      // Start the thread.
      StaticCaller.Start();

      Console.WriteLine("The Main() thread calls this after starting the new StaticCaller threads.");

      return 0;
   }
}
The ThreadStart delegate has no parameters or return value. This means that you cannot start a thread using a method that takes parameters, or obtain a return value from the method.

Примеры можно найти в MSDN

Остановка/продолжение исполнения потоков

See also: Thread.Suspend, Garbage Collection, and Safe Points

Удаление потока

The Thread.Abort method is used to stop a logical thread permanently. When you call Abort, the common language runtime throws a ThreadAbortException, which the thread can catch.

Because Thread.Abort does not cause the thread to abort immediately, you must call Thread.Join to wait on the thread if you need to be sure the thread is stopped. Join is a blocking call that does not return until the thread has actually stopped executing. Once a thread is aborted, it cannot be restarted.

You can also call Thread.Join and pass a time-out period. If the thread dies before the time out has elapsed, the call returns true. Otherwise, if the time expires before the thread dies, the call returns false. Threads that are waiting on a call to Thread.Join can be interrupted by other threads that call Thread.Interrupt.

See also: Thread Destroying at MSDN

Управление потоками

Thread.IsAlive read-only propery true if this thread has been started and has not terminated normally or aborted; otherwise, false.

Thread.Join ожидает завершения выполнения нити до ее полной остановки или в течение времени, переданного в качестве параметра.

Background threads are identical to foreground threads with one exception: a background thread will not keep the managed execution environment alive. Once all foreground threads have been stopped in a managed process (where the .exe file is a managed assembly), the system stops all background threads and shuts down. A thread can be designated as a background or a foreground thread by setting the Thread.IsBackground property.

Every thread has a thread priority assigned to it. Threads created within the common language runtime are initially assigned the priority of ThreadPriority.Normal. Threads created outside the runtime retain the priority they had before they entered the managed environment. You can get or set the priority of any thread with the Thread.Priority property.

Состояния потоков

The property Thread.ThreadState provides a bit mask that indicates the thread's current state. A thread is always in at least one of the possible states in the ThreadState? enumeration, and can be in multiple states at the same time.

When you create a managed thread it is in the Unstarted state. The thread remains in the Unstarted state until it is moved into the started state by calling Thread.Start. Unmanaged threads that enter the managed environment are already in the started state. Once in the started state, there are a number of actions that can cause the thread to change states. The following table lists the actions that cause a change of state, along with the corresponding new state.

Action Resulting new state
Another thread calls Thread.Start. Unchanged
The thread responds to Thread.Start and starts running. Running
The thread calls Thread.Sleep. WaitSleepJoin
The thread calls Monitor.Wait on another object. WaitSleepJoin
The thread calls Thread.Join on another thread. WaitSleepJoin
Another thread calls Thread.Suspend. SuspendRequested
The thread responds to a Thread.Suspend request. Suspended
Another thread calls Thread.Resume. Running
Another thread calls Thread.Interrupt. Running
Another thread calls Thread.Abort. AbortRequested
The thread responds to a Thread.Abort. Aborted
Threads are often in more than one state at any given time. For example, if a thread is blocked from a Wait call and another thread calls Abort on that same thread, the thread will be in both the WaitSleepJoin and the AbortRequested state at the same time. In that case, as soon as the thread returns from the call to Wait or is interrupted, it will receive the ThreadAbortException.

Once a thread leaves the Unstarted state as the result of a call to Thread.Start, it can never return to the Unstarted state. A thread can never leave the Stopped state, either.

ThreadPool

You can use thread pooling to make much more efficient use of multiple threads, depending on your application. Many applications use multiple threads, but often those threads spend a great deal of time in the sleeping state waiting for an event to occur.

See also: ThreadPool

Лучшие практики

http://msdn.microsoft.com/library/en-us/cpguide/html/cpconmanagedthreadingbestpractices.asp?frame=true

Dead Lock

Race Conditions

Recomendations

Timer

http://msdn.microsoft.com/library/en-us/cpguide/html/cpcontimer.asp?frame=true

Синхронизация нескольких потоков

Monitor/lock

Monitor objects expose the ability to synchronize access to a region of code by taking and releasing a lock on a particular object using the Monitor.Enter, Monitor.TryEnter, and Monitor.Exit methods. Once you have a lock on a code region, you can use the Monitor.Wait, Monitor.Pulse, and Monitor.PulseAll methods. Wait releases the lock if it is held and waits to be notified. When Wait is notified, it returns and obtains the lock again. Both Pulse and PulseAll? signal for the next thread in the wait queue to proceed.

Monitor locks objects (that is, reference types), not value types. While you can pass a value type to Enter and Exit, it is boxed separately for each call. Since each call creates a separate object, Enter never blocks, and the code it is supposedly protecting is not really synchronized.

using System;
using System.Threading;

public class ThreadingExample : Object {
        public static void Main() {
                Thread[] threads = new Thread[2];
                for( int count=0;count<2; count++) {
                        threads[count] = new Thread( new ThreadStart( Count ) );
                        threads[count].Start();
                }      
        }
        
//        public static Object synchronizeVariable = "locking variable";
        
        public static void Count() {
                lock( typeof(ThreadingExample) ) {
                        for( int count=1;count<=5;count++ ) {
                                Console.WriteLine( count + " " ); 
                                Monitor.PulseAll( typeof(ThreadingExample) );
                                if( count < 5 ) Monitor.Wait( typeof(ThreadingExample) );
                        }
                }
        }
}

WaitHandle?

http://msdn.microsoft.com/library/en-us/cpguide/html/cpconwaithandle.asp?frame=true

Mutex

http://msdn.microsoft.com/library/en-us/cpguide/html/cpconmutex.asp?frame=true

interlocked

The Interlocked methods CompareExchange, Decrement, Exchange, and Increment provide a simple mechanism for synchronizing access to a variable that is shared by multiple threads. The threads of different processes can use this mechanism if the variable is in shared memory.

The Increment and Decrement functions combine the operations of incrementing or decrementing the variable and checking the resulting value. This atomic operation is useful in a multitasking operating system, in which the system can interrupt one thread's execution to grant a slice of processor time to another thread.

The Exchange function atomically exchanges the values of the specified variables. The CompareExchange function combines two operations: comparing two values and storing a third value in one of the variables, based on the outcome of the comparison.

To update an object type variable only if it is null. You can use the following code to update the variable and make the code thread safe.

if (x == null)
{
   lock (this)
   {
      if (x == null)
      {
         x = y;
      }
   }
}
You can improve the performance of the previous sample by replacing it with the following code.
System.Threading.Interlocked.CompareExchange(ref x, y, null);

ReaderWriterLock?

http://msdn.microsoft.com/library/en-us/cpguide/html/cpconreaderwriterlock.asp?frame=true

Ссылки

-- AndreyUstyuzhanin - 06 Apr 2004