Threads
Added in .NET 1.1
A thread is a unit of scheduling
Threads can only have one task running at a time.
Threads typically represent multiple flows of execution in an application (or process)
If you are creating more than one thread you need to have a 1:1 relationship between the number of threads and the number of cores in the CPU.
Each thread can run in a separate processor on a multi-processor computer.
By default each thread executes independently
System.Threading.Thread myThread;
myThread = new System.Threading.Thread(delegate() {});
myThread.Start();
Using Threads
Every time you create a thread it uses up more memory.
In fact about 1 MB of memory per thread.
When you are using threads you should always use a Thread Pool to help recycle the threads.
Thread Priority
Highest
AboveNormal
Normal
BelowNorml
Lowest
Thread Class
The Thread class is used for creating and manipulating a thread in Windows.
Application Domains
An App Domain can contain one or more threads.
Threads can cross between different App Domains.
Applications > Application Domains
STA - Single Threaded Apartment
System.Threading.Thread.CurrentThread.TrySetApartmentState
This sets the apartment state of a thread.
(System.Threading.ApartmentState.STA)
Returns true if the apartment state is set
Returns true if the apartment state is not set
Single Threaded Apartment
The STA (Single Threaded Apartment) can have only one thread.
When an object created on a STA thread is called from another thread, this other thread posts windows messages into the message queue of the STA thread.
You can use Queues with multi-threading in .NET but you must use the necessary lock statements.
Lots of windows code requires you to follow STA
such as: clipboard, drag and drop, windows common dialog boxes, UI thread for WPF, windows forms, multiple windows
Threads are initialized as ApartmentState.MTA by default.
The apartment state cannot be changed once the thread has been created.
Communication between apartments is done via marshalling
System.Threading.Thread.CurrentThread
Thread Safe Blocking Queue.
Interlocked classes
Methods
Abort - this raises a ThreadAbortException. Aborting a thread should be avoided. A thread should always stop by itself by completing its function
GetApartmentState
GetData
Interrupt
Join - in this case the calling thread waits until the other threads have finished
Resume (obselete, not used)
SetApartmentState - sets the apartment state of a thread before it is started
SetData
Sleep - the thread transitions to the WaitSleepJoin state. A thread can only put itself to sleep (another thread cannot do it)
Suspend (obselete, not used)
Yield
Properties
CurrentContext
CurrentThread
IsAlive
IsBackground
IsThreadPoolThread
Name
Priority
ThreadState
A managed thread can create a single threaded apartment that allows only one thread or a multi-threaded apartment that allows more than one thread.
You can control which type of apartment will be used in a thread by specifying the ApartmentState property before the thread is started.
Simple Example
public class SimpleThreadExample
{
public void StartMultipleThread()
{
DateTime startTime = DateTime.Now;
Thread t1 = new Thread(() =>
{
int numberOfSeconds = 0;
while (numberOfSeconds < 5)
{
Thread.Sleep(1000);
numberOfSeconds++;
}
System.Console.WriteLine("I ran for 5 seconds");
});
Thread t2 = new Thread(() =>
{
int numberOfSeconds = 0;
while (numberOfSeconds < 8)
{
Thread.Sleep(1000);
numberOfSeconds++;
}
System.Console.WriteLine("I ran for 8 seconds");
});
//parameterized thread
Thread t3 = new Thread(p =>
{
int numberOfSeconds = 0;
while (numberOfSeconds < Convert.ToInt32(p))
{
Thread.Sleep(1000);
numberOfSeconds++;
}
System.Console.WriteLine("I ran for {0} seconds", numberOfSeconds);
});
t1.Start();
t2.Start();
//passing parameter to parameterized thread
t3.Start(20);
//wait for t1 to fimish
t1.Join();
//wait for t2 to finish
t2.Join();
//wait for t3 to finish
t3.Join();
System.System.Console.WriteLine("All Threads Exited in {0} secods", (DateTime.Now - startTime).TotalSeconds);
}
}
Thread.Abort() method can be used to destroy the running thread. But in practice, it is better to let the CLR do the dirty work for you. You can have a flag which would break your long running process.
public class DestroyThreadExample
{
public bool IsCancelled { get; set; }
public Thread MyThread { get; set; }
public void StartThread()
{
MyThread = new Thread(() =>
{
int numberOfSeconds = 0;
while (numberOfSeconds < 8)
{
if (IsCancelled == false)
{
break;
}
Thread.Sleep(1000);
numberOfSeconds++;
}
System.Console.WriteLine("I ran for {0} seconds", numberOfSeconds);
});
}
//Destroy thread
public void Abort()
{
MyThread.Abort();
}
//Graceful exit
public void GracefulAbort()
{
IsCancelled = true;
}
}
Responsive UI
public partial class Form1 : Form
{
public delegate void UpdateLabel(string label);
public bool IsCancelled { get; set; }
public Form1()
{
InitializeComponent();
}
private void UpdateUI(string labelText)
{
lblStopWatch.Text = labelText;
}
private void btnStart_Click(object sender, EventArgs e)
{
DateTime startTime = DateTime.Now;
IsCancelled = false;
Thread t = new Thread(() =>
{
while (IsCancelled==false)
{
Thread.Sleep(1000);
string timeElapsedInstring = (DateTime.Now - startTime).ToString(@"hh\:mm\:ss");
lblStopWatch.Invoke(new UpdateLabel(UpdateUI), timeElapsedInstring);
}
});
t.Start();
}
private void btnStop_Click(object sender, EventArgs e)
{
IsCancelled = true;
}
}
Noticed the delegate being used to update the UI?
lblStopWatch.Invoke(new UpdateLabel(UpdateUI), timeElapsedInstring);
This is because you can't update UI from any other thread other than the UI thread. I think this is very common scenario every one would have dealt with while working on windows forms.
Background Worker
In this approach, instead of creating the plain old thread and using delegate to update the UI, we have BackgroundWorker component which does the work for us. It supports multiple events to run long running process (DoWork), update the UI (ProgressChanged) and you will know when the background thread has actually ended (RunWorkerCompleted). In the plain old thread, knowing the end of the thread is tricky and you have to rely either of Thread. Join or use some other wait handles.
Lock
Problems Introduced by Threading
While writing the multi-threaded application, there are a bunch of known issues that we should be able to handle. Deadlocks and race conditions are few to name. It is necessary to maintain synchronized access to different resources to make sure we are not corrupting the output. For example, if a file in the filesystem is being modified by multiple threads, the application must allow only one thread to modify the file at a time, otherwise the file might get corrupted. One way of doing it is using Lock keyword. If we are accessing the shared resource around the Lock statement, it will allow only one thread to execute the code within the lock block.
In this example we have two threads accessing the same resource. Using the lock keyword will guarantee that the shared variable will be accessed by one one thread at a time. While thread t1 is executing the code within the lock block, thread t2 will be waiting.
public class LockExample
{
public int SharedResource { get; set; }
public object _locker = 0;
public void StartThreadAccessingSharedResource()
{
Thread t1 = new Thread(() =>
{
lock (_locker)
{
SharedResource++;
}
});
Thread t2 = new Thread(() =>
{
lock (_locker)
{
SharedResource--;
}
});
t1.Start();
t2.Start();
}
}
Pattern - Form OnClick
Open Visual Studio 2022 as Administrator.
Create a New Project, language C#, platform Windows, project type Desktop.
Find "Windows Forms App" and press Next
Change the Project Name to "WinForms_Threading_BWOnClick".
Check / change the Location if necessary.
Press Next.
Check / Change the Framework to .NET 6.0.
Press Create.
This will create a solution that contains two projects
Create a Windows Form called "UsingThread"
On the Windows Form add two buttons and text box
SS
You can only update the UI from the UI thread.
Add the following code
public partial class UsingThread : System.Windows.Forms.Form
{
public delegate void UpdateTextBox(string label);
public bool isRunning { get; set; }
public UsingThread()
{
InitializeComponent();
}
public void UpdateUI(string labelText)
{
this.txtStatusBox.Text = labelText + System.Environment.NewLine + this.txtStatusBox.Text;
}
private void btnStop_Click(object sender, EventArgs e)
{
isRunning = false;
}
private void btnStart_Click(object sender, EventArgs e)
{
System.DateTime startTime = System.DateTime.Now;
isRunning = true;
System.Threading.Thread t = new System.Threading.Thread(() =>
{
while (isRunning == true)
{
System.Threading.Thread.Sleep(1000);
string timeElapsedInString = (System.DateTime.Now - startTime).ToString(@"hh\:mm\:ss");
this.txtStatusBox.Invoke(new UpdateTextBox(UpdateUI), timeElapsedInString);
}
});
t.Start();
}
}
COM Interop
Whenever you are doing COM Interop you must use a STA thread.
If you want to access a COM object from a thread, that thread must belong to an apartment.
COM provides threading guarantees for classes
COM objects can be used in threads.
1 Process - 1 STA
Also called Apartment Model
Is a set of COM objects associated with a single thread inside a process
The thread must retrieve and dispatch window messages
1 Process - Many STAs threads
A process can have more than one STA threads
Interface pointers are marshaled between the threads.
1 Process - 1 MTA
Added in .NET 4.0
Is a set of COM objects associated with multiple threads inside a process.
Interface pointers are NOT marshaled between the threads.
1 Process - 1 MTA, many STAs
Also called Mixed Model
STAThreadAttribute Class
System.STAThreadAttribute
Indicates that the COM threading model for an application is single-threaded apartment (STA).
Apply this attribute to the entry point method (the Main() method in C# and Visual Basic).
It has no effect on other methods.
COM threading models only apply to applications that use COM interop.
The COM threading model can be set to single-threaded apartment or multithreaded apartment.
The application thread is only initialized for COM interop if the thread actually makes a call to a COM component.
If COM interop is not used, then the thread is not initialized, and the STAThreadAttribute attribute, if it is present, has no effect.
Starting with the .NET Framework version 2.0, the default threading model for COM interop depends on the language in which you are developing your application:
Visual Basic - Single-threaded apartment
C# - Multithreaded apartment
C++ - Multithreaded apartment
To change these defaults, you use the STAThreadAttribute attribute to set the threading model for the application, or call the Thread.SetApartmentState or Thread.TrySetApartmentState method before starting the thread to set the threading model for a particular thread.
ThreadStart Delegate
This represents the method that should be executed on a thread
This delegate is passed to the Thread constructor
This method is not called until the Thread.Start method is called
System.Threading.ThreadStart myThreadStart;
myThreadStart = new System.Threading.ThreadStart(myMethod);
System.Threading.Thread myThread;
myThread = new System.Threading.Thread( myThreadStart );
myThread.Start();
for (int i=0; i<100; i++)
{
Console.Write("main thread");
}
void myMethod()
{
for (int i=0; i<100; i++)
{
Console.Write("second thread");
}
}
ParameterizedThreadStart Delegate
This has an argument of data type object.
This delegate only allows you to pass in a single parameter and does so in an unsafe way because it passes it as an object.
System.Threading.Thread myThread;
myThread = new System.Threading.Thread (new Systm.Threading.ParameterizedThreadStart(myMethod) );
public static void myMethod(object myParam)
{}
myThread.Start(myObjectParameter);
Passing Data in Parameters
Avoid using the ParameterizedThreadStart Delegate
You should use the ThreadStart delegate in conjunction with anonymous methods or a lambda function.
System.Threading.Thread myThread;
myThread = new System.Threading.Thread ( () => myMethod(200, "some text"));
myThread.Start();
public static void myMethod(int param1, string param2) {}
Passing Data Between
If we want to share data between threads we can use a common reference or a static field
You can share data between different threads by using a public or static field.
public static void Main()
{
MyClass obj = new MyClass();
obj.message = "some text";
new System.Threading.Thread (obj.Method_Run).Start();
Console.ReadLine();
System.Console.WriteLine(obj.newtext):
}
public class MyClass()
{
public string mytext;
public string newtext;
public void Method_Run()
{
System.Console.WriteLine(mytext);
newtext = "new text";
}
}
Locking Mechanisms
System.Threading.Monitor
Monitor.Enter()
finally
{
Monitor.Exit();
}
lock keyword
This is a shortcut to the Monitor methods in conjunction with the finally block
lock()
{
}
System.Threading.Mutex
Similar to Monitor but this works across multiple processes
mut = new Mutex();
mut.WaitOne
mut.ReleaseMutex
IsBackground Property
Gets or sets a value indicating whether or not a thread is a background thread
All threads created by calling a Thread class constructor execute in the foreground by default
All threads which are part of a pool of worker threads maintained by the runtime execute in the background my default.
System.Threading.Thread myThread;
myThread = new System.Threading.Thread(delegate() {});
myThread = new System.Threading.Thread( new System.Threading.ThreadStart(myMethod) );
public static void myMethod()
{}
myThread.SetApartmentState(System.Threading.Apartment.STA);
myThread.IsBackground = true;
myThread.Start()
Volatile Fields
By default all fields are subjects to compiler optimisations
(for example storing simple variables in processor registers)
We can use the volatile keyword to disable these optimisations
If we do not disable this optimisation there is a chance the fields will get incorrect values
If you do not use the volatile keyword the static field will not be shared between the threads.
Thread States
UnStarted - the thread has just been created and is ready for execution
Running - the thread is executing
SleepWaitJoin - the thread is waiting on some event to occur, when this event occurs the thread will be put back into running state
Suspended - (now obselete) the thread has been stopped by another thread it will be put back into running state when it is resumed
Stopped - the threads life has ended, caused by the normal termination of the thread function or by the thread being aborted
Joining Threads
Interrupting
Thread.Interrupt
ThreadInterrupedException
Thread.Abort - terminates a thread raising a ThreadAbortException
System.Threading.Semaphore
This is a synchronisation tool for both threads and processes and can be used to restrict access to shared resources.
This is for controlling the sequence of events
It is usually an abstract data type or integer variable
StandardOleMarshalObject Class
.NET Library > System.Runtime.InteropServices
Replaces the standard common language runtime MTA marshaler with the standard OLE STA marshaler
StandardOleMarshalObject only keeps things on the main thread if you create the object on a STA thread.
This class prevents calls made into a hosting object by OLE from entering threads other than the UI thread.
Any public static members of this type are thread safe
Any instance members are not guaranteed to be thread safe
If you expose your add-in through the COMAddIn.Object method either explicitly (non VSTO) or implicitly (with VSTO through RequestComAddInAutomationService) you might run into threading issues
Scenarios:
1) Being called from VBA - OK
2) Being called from another add-in in the same process
3) Being called from another process or thread there is a problem. By default these will not be executed on the main UI thread.
From a COM perspective all managed objects are thread neutral (by default)
What we want is to tell COM that our managed object lives in a Single Threaded Apartment (STA).
Needed for COMVisible interfaces passing List<string>
Necessary when working with generics
© 2026 Better Solutions Limited. All Rights Reserved. © 2026 Better Solutions Limited TopPrevNext