C# Multithreading Journey (2) – Creating and Starting Threads
This article explains how to create and start threads in C#, covering ThreadStart delegates, lambda expressions, passing parameters, naming threads, foreground/background distinctions, thread priority, and exception handling, with clear code examples and best‑practice recommendations.
This tutorial demonstrates the fundamentals of multithreading in C# by showing how to create and start threads using the Thread class and the ThreadStart delegate.
Thread creation and start – A thread is instantiated with a ThreadStart delegate (or a method group) and started by calling Start() . Example:
// ThreadStart delegate definition
[ComVisible(true)]
public delegate void ThreadStart();
class ThreadTest {
static void Main() {
Thread t = new Thread(new ThreadStart(Go));
t.Start();
Go();
Console.ReadKey();
}
static void Go() {
Console.WriteLine("hello!");
}
}The thread runs Go() concurrently with the main thread, producing two near‑simultaneous "hello!" outputs.
Passing data to a thread – Data can be supplied via lambda expressions, anonymous methods, or the ParameterizedThreadStart delegate. Examples:
// Lambda with a single argument
Thread t = new Thread(() => Print("A"));
t.Start();
static void Print(string message) { Console.WriteLine(message); }
// Multiple arguments using a lambda block
new Thread(() => {
Console.WriteLine("a");
Console.WriteLine("b");
}).Start();
// ParameterizedThreadStart
Thread t = new Thread(Print);
t.Start("A");
static void Print(object obj) {
string message = (string)obj; // conversion required
Console.WriteLine(message);
}When using lambdas, be careful with captured variables; each iteration should capture a separate copy to avoid nondeterministic output.
// Problematic capture
for (int i = 0; i < 10; i++) {
new Thread(() => Console.Write(i)).Start();
}
// Correct solution
for (int i = 0; i < 10; i++) {
int temp = i;
new Thread(() => Console.Write(temp)).Start();
}Naming threads – Assign a name via the Name property for easier debugging (e.g., Thread.CurrentThread.Name = "Main Thread"; ).
static void Main(string[] args) {
Thread.CurrentThread.Name = "Main Thread";
Thread t = new Thread(Go);
t.Name = "Worker Thread";
t.Start();
Go();
Console.ReadKey();
}
static void Go() {
Console.WriteLine("Go! The current thread is {0}", Thread.CurrentThread.Name);
}Foreground vs. background threads – Threads created by the user are foreground by default and keep the process alive; background threads terminate when all foreground threads finish. The IsBackground property controls this behavior.
Thread t = new Thread(() => Console.ReadKey());
if (args.Length > 0) {
// background thread
t.IsBackground = true;
}
t.Start();Thread priority – The ThreadPriority enum (Lowest, BelowNormal, Normal, AboveNormal, Highest) influences the scheduler’s allocation of CPU time. Raising priority should be done cautiously to avoid starving other threads.
public enum ThreadPriority {
Lowest = 0,
BelowNormal = 1,
Normal = 2,
AboveNormal = 3,
Highest = 4,
}Process priority can be raised with Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High; , but this affects the entire application and may impact UI responsiveness.
using (Process p = Process.GetCurrentProcess()) {
p.PriorityClass = ProcessPriorityClass.High;
}Exception handling in threads – Exceptions thrown inside a thread are not caught by a surrounding try/catch in the creator method. Each thread should have its own exception handling logic.
static void Main(string[] args) {
try {
new Thread(Go).Start();
} catch (Exception ex) {
Console.WriteLine("Exception");
}
Console.ReadKey();
}
static void Go() {
throw null; // will terminate the thread unhandled
}Correct approach: place the try/catch inside the thread method itself.
static void Main(string[] args) {
new Thread(Go).Start();
Console.ReadKey();
}
static void Go() {
try {
throw null;
} catch (Exception ex) {
Console.WriteLine(ex.Message);
}
}For UI applications, global handlers like Application.ThreadException only catch UI thread exceptions; background thread exceptions must be handled manually or via AppDomain.CurrentDomain.UnhandledException , which cannot prevent process termination.
Wukong Talks Architecture
Explaining distributed systems and architecture through stories. Author of the "JVM Performance Tuning in Practice" column, open-source author of "Spring Cloud in Practice PassJava", and independently developed a PMP practice quiz mini-program.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.