A thread is an independent executor, a basic unit to which an operating system allocates processor time.
Each thread has its own independent stack but shares the same memory with all the other threads in a process.
A process is an executing program. Each process has multiple threads in it, and each of those threads can be doing different things simultaneously.
A thread preemption refers to the interruption of a currently executing thread by the operating system's scheduler in order to give execution time to another thread.
A time-slicing is rapid switching of execution between active threads done by a thread scheduler on a single-processor computer.
Under Windows, a time-slice is typically in the tens-of-milliseconds region — much larger than the CPU overhead in actually switching context between one thread and another, which is typically in the few-microseconds region.
In some applications, there is one thread that is special. For example, user interface applications have a single special UI thread, and console applications have a single special main thread.
The ↑ Thread
class creates and controls a thread, sets its priority, and gets its status.
The ↑ Thread.Sleep
method suspends the current thread for the specified amount of time.
A thread, while blocked, doesn't consume CPU resources. While waiting on a Thread.Sleep
or Thread.Join
, a thread is blocked and so does not consume CPU resources.
Thread.Sleep(0)
relinquishes the thread's current time slice immediately, voluntarily handing over the CPU to other threads. Thread.Yield()
method does the same thing — except that it relinquishes only to threads running on the same processor.
The ↑ Thread.Join
method blocks the calling thread until the thread represented by this instance terminates.
The ↑ Thread.Yield
method causes the calling thread to yield execution to another thread that is ready to run on the current processor. The operating system selects the thread to yield to.
Thread.Sleep(0)
or Thread.Yield
is occasionally useful in production code for advanced performance tweaks. It's also an excellent diagnostic tool for helping to uncover thread safety issues: if inserting Thread.Yield()
anywhere in your code makes or breaks the program, you almost certainly have a bug.
The ↑ Thread.SpinWait
method causes a thread to wait the number of times defined by the iterations
parameter.
The SpinWait
method is useful for implementing locks. Classes, such as Monitor
and ReaderWriterLock
, use this method internally. SpinWait
essentially puts the processor into a very tight loop, with the loop count specified by the iterations
parameter. The duration of the wait therefore depends on the speed of the processor.
Contrast this with the Sleep
method. A thread that calls Sleep
yields the rest of its current slice of processor time, even if the specified interval is zero. Specifying a non-zero interval for Sleep
removes the thread from consideration by the thread scheduler until the time interval has elapsed.
SpinWait is not generally useful for ordinary applications. In most cases, you should use the synchronization classes provided by the .NET Framework; for example, call Monitor.Enter
or lock
The ↑ Thread.Interrupt
method interrupts a thread that is in the ↑ WaitSleepJoin
thread state.
Calling Interrupt
on a blocked thread forcibly releases it, throwing a ThreadInterruptedException
:
var thread = new Thread(() =>
{
try
{
Thread.Sleep(5000);
Console.WriteLine("Start");
}
catch (ThreadInterruptedException exception)
{
Console.WriteLine("Thread was interrupted from a waiting state");
}
Console.WriteLine("Continuing thread execution...");
});
thread.Start();
thread.Interrupt();
Interrupting a thread does not cause the thread to end, unless the ThreadInterruptedException
is unhandled.
If Interrupt
is called on a thread that's not blocked, the thread continues executing until it next blocks, at which point a ThreadInterruptedException
is thrown.
Nowadays Interrupt
is unnecessary: if you are writing the code that blocks, you can achieve the same result more safely with a signaling construct — or Framework 4.0's cancellation tokens.
The ↑ Thread.Abort
method raises a ThreadAbortException
in the thread on which it's invoked, to begin the process of terminating the thread. Calling this method usually terminates the thread.
The Thread.Abort
APIs are ↑ obsolete. Starting in .NET 5, calling this method produces compiler warning SYSLIB0006
.
Use a CancellationToken
to abort processing of a unit of work instead of calling Thread.Abort
.
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.
var worker = new Thread(() => Console.ReadLine())
{
// IsBackground = true
};
worker.Start();
A thread's foreground/background status has no relation to its priority or allocation of execution time.
The default stack size is ↑ 1 megabyte.
Here is just an example that creates a thread with stack size of 30 MBs:
var thread = new Thread(
start: () =>
{
Span<int> numbers = stackalloc int[7_000_000]; // 4 bytes x 7 million records = 28 megabytes
Console.WriteLine("Allocated an array of integers on the stack");
},
maxStackSize: 30_000_000 // 30 megabytes
);
thread.Start();
thread.Join();
Code above just prints:
Allocated an array of integers on the stack
However, if you try to allocate an array of 8 millions of records, you get ↑ StackOverflowException
:
Stack overflow.
at Program+<>c.<<Main>$>b__0_0()
at System.Threading.Thread.StartCallback()