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:

Communicate over a network, to a Web server, and to a database.

Perform operations that take a large amount of time.

Distinguish tasks of varying priority. For example, a high-priority thread manages time-critical tasks, and a low-priority thread performs other tasks.

Allow the user interface to remain responsive, while allocating time to background 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:

The system consumes memory for the context information required by processes, AppDomain? objects, and threads. Therefore, the number of processes, AppDomain? objects, and threads that can be created is limited by available memory.

Keeping track of a large number of threads consumes significant processor time. If there are too many threads, most of them will not make significant progress. If most of the current threads are in one process, threads in other processes are scheduled less frequently.

Controlling code execution with many threads is complex, and can be a source of many bugs.

Destroying threads requires knowing what could happen and handling those issues.

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:

System resources (such as communications ports).

Resources shared by multiple processes (such as file handles).

The resources of a single application domain (such as global, static, and instance fields) accessed by multiple threads.

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

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:

If you need a task to have a particular priority.

If you have a task that might run a long time (and therefore block other tasks).

If you need to place threads into a single-threaded apartment (all ThreadPool threads are in the multithreaded apartment).

If you need a stable identity associated with the thread. For example, you should use a dedicated thread to abort that thread, suspend it, or discover it by name.

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

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;
publicclass ServerClass{
// The method that will be called when the thread is started.publicvoid 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.");
}
publicstaticvoid 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.");
}
}
publicclass Simple{
publicstaticint 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.

To pass data to a thread, create an object to hold the data and the thread method, as shown in the two code examples that follow.

To retrieve the results of a thread method, you can use a callback method

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

Thread.Sleep causes the current thread to immediately block for the number of milliseconds you pass to Sleep, yielding the remainder of its time slice to another thread. Calling Thread.Sleep(Timeout.Infinite) causes a thread to sleep until it is interrupted or aborted by another thread (Thread.Interrupt, Thread.Abort) (NB One thread cannot call Sleep on another thread )

When a thread calls Thread.Suspend on itself, the call blocks until the thread is resumed by another thread. When one thread calls Thread.Suspend on another thread, the call is a nonblocking call that causes the other thread to pause.

Calling Thread.Resume breaks another thread out of the suspend state and causes the thread to resume execution, regardless of how many times Thread.Suspend was called. (NB Unlike Thread.Sleep, Thread.Suspend does not cause a thread to immediately stop execution. The common language runtime must wait until the thread has reached a safe point).

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

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.

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.

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.

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.

Dead Lock

Race Conditions

Recomendations

Timer

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

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.

Mutex

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.